#!/bin/bash

# Prowler is a tool that provides automate auditing and hardening guidance of an AWS account.
# It is based on AWS-CLI commands. It follows guidelines present in the CIS Amazon
# Web Services Foundations Benchmark at:
# https://d0.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf

# This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0
# International Public License. The link to the license terms can be found at
# https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
#
# Author: Toni de la Fuente - @ToniBlyx / Alfresco Software Inc.

# Prowler - Iron Maiden
#
# Walking through the city, looking oh so pretty
# I've just got to find my way
# See the ladies flashing
# All there legs and lashes
# I've just got to find my way...

# Exit if a pipeline results in an error.
# set -ue
# set -o pipefail
# set -vx
# Exits if any error is found
# set -e

OPTRED="[1;31m"
OPTNORMAL="[0;39m"

# Set the defaults for these getopts variables
REGION="us-east-1"
FILTERREGION=""
MAXITEMS=100
MONOCHROME=0
MODE="text"
SEP=','
KEEPCREDREPORT=0
EXITCODE=0


# Command usage menu
usage(){
  echo "
USAGE:
      `basename $0` -p <profile> -r <region> [ -h ]

  Options:
      -p <profile>        specify your AWS profile to use (i.e.: default)
      -r <region>         specify an AWS region to direct API requests to
                            (i.e.: us-east-1), all regions are checked anyway
      -c <check_id>       specify a check number or group from the AWS CIS benchmark
                            (i.e.: "check11" for check 1.1, "check3" for entire section 3, "level1" for CIS Level 1 Profile Definitions or "forensics-ready")
      -f <filterregion>   specify an AWS region to run checks against
                            (i.e.: us-west-1)
      -m <maxitems>       specify the maximum number of items to return for long-running requests (default: 100)
      -M <mode>           output mode: text (defalut), mono, csv (separator is ","; data is on stdout; progress on stderr)
      -k                  keep the credential report
      -n                  show check numbers to sort easier
                            (i.e.: 1.01 instead of 1.1)
      -l                  list all available checks only (does not perform any check)
      -e                  exclude extras
      -h                  this help
  "
  exit
}

while getopts ":hlkp:r:c:f:m:M:en" OPTION; do
   case $OPTION in
     h )
        usage
        EXITCODE=1
        exit $EXITCODE
        ;;
     l )
        PRINTCHECKSONLY=1
        ;;
     k )
        KEEPCREDREPORT=1
        ;;
     p )
        PROFILE=$OPTARG
        ;;
     r )
        REGION=$OPTARG
        ;;
     c )
        CHECKNUMBER=$OPTARG
        ;;
     f )
        FILTERREGION=$OPTARG
        ;;
     m )
        MAXITEMS=$OPTARG
        ;;
     M )
        MODE=$OPTARG
        ;;
     n )
        NUMERAL=1
        ;;
     e )
        EXTRAS=1
        ;;
     : )
        echo ""
        echo "$OPTRED ERROR!$OPTNORMAL  -$OPTARG requires an argument"
        usage
        EXITCODE=1
        exit $EXITCODE
        ;;
     ? )
        echo ""
        echo "$OPTRED ERROR!$OPTNORMAL Invalid option"
        usage
        EXITCODE=1
        exit $EXITCODE
        ;;
   esac
done

if [[ $MODE != "mono" && $MODE != "text" && $MODE != "csv" ]]; then
  echo ""
  echo "$OPTRED ERROR!$OPTNORMAL Invalid output mode.  Choose text, mono, or csv."
  usage
  EXITCODE=1
  exit $EXITCODE
fi

if [[ "$MODE" == "mono" || "$MODE" == "csv" ]]; then
  MONOCHROME=1
fi

if [[ $MONOCHROME -eq 1 ]]; then
  # Colors
  NORMAL=''
  WARNING=''          # Bad (red)
  SECTION=''          # Section (yellow)
  NOTICE=''           # Notice (yellow)
  OK=''               # Ok (green)
  BAD=''              # Bad (red)
  CYAN=''
  BLUE=''
  BROWN=''
  DARKGRAY=''
  GRAY=''
  GREEN=''
  MAGENTA=''
  PURPLE=''
  RED=''
  YELLOW=''
  WHITE=''
else
  # Colors
  # NOTE: Your editor may NOT show the 0x1b / escape character left of the '['
  NORMAL="[0;39m"
  WARNING="[1;33m"          # Bad (red)
  SECTION="[1;33m"          # Section (yellow)
  NOTICE="[1;33m"           # Notice (yellow)
  OK="[1;32m"               # Ok (green)
  BAD="[1;31m"              # Bad (red)
  CYAN="[0;36m"
  BLUE="[0;34m"
  BROWN="[0;33m"
  DARKGRAY="[0;30m"
  GRAY="[0;37m"
  GREEN="[1;32m"
  MAGENTA="[1;35m"
  PURPLE="[0;35m"
  RED="[1;31m"
  YELLOW="[1;33m"
  WHITE="[1;37m"
fi

SCRIPT_START_TIME=$( date -u +"%Y-%m-%dT%H:%M:%S%z" )

# Functions to manage dates depending on OS
if [ "$OSTYPE" == "linux-gnu" ] || [ "$OSTYPE" == "linux-musl" ]; then
  TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX)
  # function to compare in days, usage how_older_from_today date
  # date format %Y-%m-%d
  how_older_from_today()
    {
      DATE_TO_COMPARE=$1
      TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
      DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
      DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
      echo $DAYS_SINCE
    }
  # function to convert from timestamp to date, usage timestamp_to_date timestamp
  # output date format %Y-%m-%d
  timestamp_to_date()
    {
      # remove fractions of a second
      TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
      OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
      echo $OUTPUT_DATE
    }
  decode_report()
    {
      base64 -d
    }
elif [[ "$OSTYPE" == "darwin"* ]]; then
  # BSD/OSX commands compatibility
  TEMP_REPORT_FILE=$(mktemp -t prowler.cred_report-XXXXXX)
  how_older_from_today()
      {
        DATE_TO_COMPARE=$1
        TODAY_IN_DAYS=$(date +%s)
        DATE_FROM_IN_DAYS=$(date -jf %Y-%m-%d $DATE_TO_COMPARE +%s)
        DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
        echo $DAYS_SINCE
      }
  timestamp_to_date()
    {
      # remove fractions of a second
      TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
      OUTPUT_DATE=$(date -r $TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
      echo $OUTPUT_DATE
    }
  decode_report()
    {
      base64 -D
    }
elif [[ "$OSTYPE" == "cygwin" ]]; then
  # POSIX compatibility layer and Linux environment emulation for Windows
  TEMP_REPORT_FILE=$(mktemp -t -p /tmp prowler.cred_report-XXXXXX)
  how_older_from_today()
      {
        DATE_TO_COMPARE=$1
        TODAY_IN_DAYS=$(date -d "$(date +%Y-%m-%d)" +%s)
        DATE_FROM_IN_DAYS=$(date -d $DATE_TO_COMPARE +%s)
        DAYS_SINCE=$((($TODAY_IN_DAYS - $DATE_FROM_IN_DAYS )/60/60/24))
        echo $DAYS_SINCE
      }
  timestamp_to_date()
    {
      # remove fractions of a second
      TIMESTAMP_TO_CONVERT=$(echo $1 | cut -f1 -d".")
      OUTPUT_DATE=$(date -d @$TIMESTAMP_TO_CONVERT +'%Y-%m-%d')
      echo $OUTPUT_DATE
    }
  decode_report()
    {
      base64 -d
    }
else
      echo "Unknown Operating System! Valid \$OSTYPE: linux-gnu, linux-musl, darwin* or cygwin"
      echo "Found: $OSTYPE"
      EXITCODE=1
      exit $EXITCODE
fi

# It checks -p optoin first and use it as profile, if not -p provided then
# check environment variables and if not, it checks and loads credentials from
# instance profile (metadata server) if runs in an EC2 instance

if [[ $PROFILE ]]; then
  PROFILE_OPT="--profile $PROFILE"
else
  # if Prowler runs insinde an AWS instance with IAM instance profile attached
  INSTANCE_PROFILE=$(curl -s -m 1 http://169.254.169.254/latest/meta-data/iam/security-credentials/)
  if [[ $INSTANCE_PROFILE ]]; then
    AWS_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep AccessKeyId | cut -d':' -f2 | sed 's/[^0-9A-Z]*//g')
    AWS_SECRET_ACCESS_KEY_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE} | grep SecretAccessKey | cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g')
    AWS_SESSION_TOKEN=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/${INSTANCE_PROFILE}  grep Token| cut -d':' -f2 | sed 's/[^0-9A-Za-z/+=]*//g')
  fi
  if [[ $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY || $AWS_SESSION_TOKEN ]];then
    PROFILE="ENV"
    PROFILE_OPT=""
  else
    PROFILE="default"
    PROFILE_OPT="--profile $PROFILE"
  fi
fi

# AWS-CLI variables
AWSCLI=$(which aws)
if [ -z "${AWSCLI}" ]; then
  echo -e "\n$RED ERROR!$NORMAL AWS-CLI (aws command) not found. Make sure it is installed correctly and in your \$PATH\n"
  EXITCODE=1
  exit $EXITCODE
fi

TITLE_ID=""
TITLE_TEXT="CALLER ERROR - UNSET TITLE"
## Output formatting functions
textOK(){
  if [[ "$MODE" == "csv" ]]; then
    if [[ $2 ]]; then
      REPREGION=$2
    else
      REPREGION=$REGION
    fi
    echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}PASS${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
  else
    echo "      $OK OK! $NORMAL $1"
  fi
}

textNotice(){
  if [[ "$MODE" == "csv" ]]; then
    if [[ $2 ]]; then
      REPREGION=$2
    else
      REPREGION=$REGION
    fi
    echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}INFO${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
  else
    echo "      $NOTICE INFO! $1 $NORMAL"
  fi
}

textWarn(){
  EXITCODE=3
  if [[ "$MODE" == "csv" ]]; then
    if [[ $2 ]]; then
      REPREGION=$2
    else
      REPREGION=$REGION
    fi
    echo "$PROFILE${SEP}$ACCOUNT_NUM${SEP}$REPREGION${SEP}$TITLE_ID${SEP}WARNING${SEP}$ITEM_SCORED${SEP}$ITEM_LEVEL${SEP}$TITLE_TEXT${SEP}$1"
  else
    echo "      $BAD WARNING! $1 $NORMAL"
  fi
}

textTitle(){
  TITLE_ID=$1
  if [[ $NUMERAL ]]; then
    TITLE_ID=$(echo $TITLE_ID | cut -d, -f2)
  else
    TITLE_ID=$(echo $TITLE_ID | cut -d, -f1)
  fi

  TITLE_TEXT=$2

  case "$3" in
    0|No|NOT_SCORED)
      ITEM_SCORED="Not Scored"
      ;;
    1|Yes|SCORED)
      ITEM_SCORED="Scored"
      ;;
    *)
      ITEM_SCORED="Unspecified"
      ;;
  esac

  case "$4" in
    LEVEL1)  ITEM_LEVEL="Level 1";;
    LEVEL2)  ITEM_LEVEL="Level 2";;
    EXTRA)   ITEM_LEVEL="Extra";;
    SUPPORT) ITEM_LEVEL="Support";;
    *)       ITEM_LEVEL="Unspecified or Invalid";;
  esac

  if [[ "$MODE" == "csv" ]]; then
      >&2 echo "$TITLE_ID $TITLE_TEXT"
  else
    if [[ "$ITEM_SCORED" == "Scored" ]]; then
      echo -e "\n$BLUE $TITLE_ID $NORMAL $TITLE_TEXT"
    else
      echo -e "\n$PURPLE $TITLE_ID  $TITLE_TEXT $NORMAL"
    fi
  fi
}

# List of checks IDs and Titles
TITLE1="Identity and Access Management ****************************************"
ID11="1.1,1.01"
TITLE11="Avoid the use of the root account (Scored)."
ID12="1.2,1.02"
TITLE12="Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)"
ID13="1.3,1.03"
#TITLE13="Ensure credentials unused for 90 days or greater are disabled (Scored)"
TITLE13="Ensure credentials unused for 90 days or greater are disabled"
ID14="1.4,1.04"
TITLE14="Ensure access keys are rotated every 90 days or less" # also checked by Security Monkey
ID15="1.5,1.05"
TITLE15="Ensure IAM password policy requires at least one uppercase letter"
ID16="1.6,1.06"
TITLE16="Ensure IAM password policy require at least one lowercase letter (Scored)"
ID17="1.7,1.07"
TITLE17="Ensure IAM password policy require at least one symbol (Scored)"
ID18="1.8,1.08"
TITLE18="Ensure IAM password policy require at least one number (Scored)"
ID19="1.9,1.09"
TITLE19="Ensure IAM password policy requires minimum length of 14 or greater (Scored)"
ID110="1.10"
TITLE110="Ensure IAM password policy prevents password reuse: 24 or greater (Scored)"
ID111="1.11"
TITLE111="Ensure IAM password policy expires passwords within 90 days or less (Scored)"
ID112="1.12"
TITLE112="Ensure no root account access key exists (Scored)"
ID113="1.13"
TITLE113="Ensure MFA is enabled for the root account (Scored)"
ID114="1.14"
TITLE114="Ensure hardware MFA is enabled for the root account (Scored)"
ID115="1.15"
TITLE115="Ensure security questions are registered in the AWS account (Not Scored)"
ID116="1.16"
TITLE116="Ensure IAM policies are attached only to groups or roles (Scored)"
ID117="1.17"
TITLE117="Enable detailed billing (Scored)"
ID118="1.18"
TITLE118="Ensure IAM Master and IAM Manager roles are active (Scored)"
ID119="1.19"
TITLE119="Maintain current contact details (Scored)"
ID120="1.20"
TITLE120="Ensure security contact information is registered (Scored)"
ID121="1.21"
TITLE121="Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)"
ID122="1.22"
TITLE122="Ensure a support role has been created to manage incidents with AWS Support (Scored)"
ID123="1.23"
TITLE123="Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)"
ID124="1.24"
TITLE124="Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)"
TITLE2="Logging ***************************************************************"
ID21="2.1,2.01"
TITLE21="Ensure CloudTrail is enabled in all regions (Scored)"
ID22="2.2,2.02"
TITLE22="Ensure CloudTrail log file validation is enabled (Scored)"
ID23="2.3,2.03"
TITLE23="Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)"
ID24="2.4,2.04"
TITLE24="Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)"
ID25="2.5,2.05"
TITLE25="Ensure AWS Config is enabled in all regions (Scored)"
ID26="2.6,2.06"
TITLE26="Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)"
ID27="2.7,2.07"
TITLE27="Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)"
ID28="2.8,2.08"
TITLE28="Ensure rotation for customer created CMKs is enabled (Scored)"
TITLE3="Monitoring ************************************************************"
ID31="3.1,3.01"
TITLE31="Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)"
ID32="3.2,3.02"
TITLE32="Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)"
ID33="3.3,3.03"
TITLE33="Ensure a log metric filter and alarm exist for usage of root account (Scored)"
ID34="3.4,3.04"
TITLE34="Ensure a log metric filter and alarm exist for IAM policy changes (Scored)"
ID35="3.5,3.05"
TITLE35="Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)"
ID36="3.6,3.06"
TITLE36="Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)"
ID37="3.7,3.07"
TITLE37="Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)"
ID38="3.8,3.08"
TITLE38="Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)"
ID39="3.9,3.09"
TITLE39="Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)"
ID310="3.10"
TITLE310="Ensure a log metric filter and alarm exist for security group changes (Scored)"
ID311="3.11"
TITLE311="Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)"
ID312="3.12"
TITLE312="Ensure a log metric filter and alarm exist for changes to network gateways (Scored)"
ID313="3.13"
TITLE313="Ensure a log metric filter and alarm exist for route table changes (Scored)"
ID314="3.14"
TITLE314="Ensure a log metric filter and alarm exist for VPC changes (Scored)"
ID315="3.15"
TITLE315="Ensure appropriate subscribers to each SNS topic (Not Scored)"
TITLE4="Networking ************************************************************"
ID41="4.1,4.01"
TITLE41="Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)"
ID42="4.2,4.02"
TITLE42="Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)"
ID43="4.3,4.03"
TITLE43="Ensure VPC Flow Logging is Enabled in all VPCs (Scored)"
ID44="4.4,4.04"
TITLE44="Ensure the default security group of every VPC restricts all traffic (Scored)"
ID45="4.5,4.05"
TITLE45="Ensure routing tables for VPC peering are \"least access\" (Not Scored)"
TITLE7="Extras ****************************************************************"
ID71="7.1,7.01"
TITLE71="Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark)"
ID72="7.2,7.02"
TITLE72="Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark)"
ID73="7.3,7.03"
TITLE73="Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)"
ID74="7.4,7.04"
TITLE74="Ensure there are no Security Groups without ingress filtering being used (Not Scored) (Not part of CIS benchmark)"
ID75="7.5,7.05"
TITLE75="Ensure there are no Security Groups not being used (Not Scored) (Not part of CIS benchmark)"
ID76="7.6,7.06"
TITLE76="Ensure there are no EC2 AMIs set as Public (Not Scored) (Not part of CIS benchmark)"
ID77="7.7,7.07"
TITLE77="Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)"
ID78="7.8,7.08"
TITLE78="Ensure there are no Public Accessible RDS instances (Not Scored) (Not part of CIS benchmark)"
ID79="7.9,7.09"
TITLE79="Check for internet facing Elastic Load Balancers (Not Scored) (Not part of CIS benchmark)"
ID710="7.10,7.10"
TITLE710="Check for internet facing EC2 Instances (Not Scored) (Not part of CIS benchmark)"
ID711="7.11,7.11"
TITLE711="Check for Publicly Accessible Redshift Clusters (Not Scored) (Not part of CIS benchmark)"
ID712="7.12,7.12"
TITLE712="Check if Amazon Macie is enabled (Not Scored) (Not part of CIS benchmark)"
ID713="7.13,7.13"
TITLE713="Check if GuardDuty is enabled (Not Scored) (Not part of CIS benchmark)"
ID714="7.14,7.14"
TITLE714="Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)"
ID715="7.15,7.15"
TITLE715="Check if Elasticsearch Service domains have logging enabled (Not Scored) (Not part of CIS benchmark)"
ID716="7.16,7.16"
TITLE716="Check if Elasticsearch Service domains allow open access (Not Scored) (Not part of CIS benchmark)"
ID717="7.17,7.17"
TITLE717="Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark)"
ID718="7.18,7.18"
TITLE718="Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark)"
ID719="7.19,7.19"
TITLE719="Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark)"
ID720="7.20,7.20"
TITLE720="Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)"
ID721="7.21,7.21"
TITLE721="Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)"
ID722="7.22,7.22"
TITLE722="Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)"

printCsvHeader() {
  >&2 echo ""
  >&2 echo "Generating \"${SEP}\" delimited report on stdout for profile $PROFILE, account $ACCOUNT_NUM"
      echo "PROFILE${SEP}ACCOUNT_NUM${SEP}REGION${SEP}TITLE_ID${SEP}RESULT${SEP}SCORED${SEP}LEVEL${SEP}TITLE_TEXT${SEP}NOTES"
}

prowlerBanner() {
  echo -e "$CYAN                          _"
  echo -e "  _ __  _ __ _____      _| | ___ _ __"
  echo -e " | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__|"
  echo -e " | |_) | | | (_) \ V  V /| |  __/ |"
  echo -e " | .__/|_|  \___/ \_/\_/ |_|\___|_|"
  echo -e " |_|$NORMAL$BLUE CIS based AWS Account Hardening Tool$NORMAL\n"
  echo -e "$YELLOW Date: $(date)"
}

# Get whoami in AWS, who is the user running this shell script
getWhoami(){
  ACCOUNT_NUM=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Account" | tr -d '"')
  if [[ "$MODE" == "csv" ]]; then
    CALLER_ARN_RAW=$($AWSCLI sts get-caller-identity --output json $PROFILE_OPT --region $REGION --query "Arn")
    if [[ 255 -eq $? ]]; then
      # Failed to get own identity ... exit
      echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
      >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
      EXITCODE=2
      exit $EXITCODE
    fi
    CALLER_ARN=$(echo $CALLER_ARN_RAW | tr -d '"')
    #printCsvHeader
    #textTitle "0.0" "Show report generation info" "NOT_SCORED" "SUPPORT"
    #textNotice "ARN: $CALLER_ARN  TIMESTAMP: $SCRIPT_START_TIME"
  else
    echo ""
    echo "This report is being generated using credentials below:"
    echo ""
    echo -e "AWS-CLI Profile: $NOTICE[$PROFILE]$NORMAL AWS API Region: $NOTICE[$REGION]$NORMAL AWS Filter Region: $NOTICE[${FILTERREGION:-all}]$NORMAL\n"
    if [[ $MONOCHROME -eq 1 ]]; then
      echo "Caller Identity:"
      $AWSCLI sts get-caller-identity --output text $PROFILE_OPT --region $REGION --query "Arn"
      if [[ 255 -eq $? ]]; then
        # Failed to get own identity ... exit
        echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
        >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
        exit 2
      fi
      echo ""
    else
      echo "Caller Identity:"
      $AWSCLI sts get-caller-identity --output table $PROFILE_OPT --region $REGION
      if [[ 255 -eq $? ]]; then
        # Failed to get own identity ... exit
        echo variable $PROFILE_OPT
        echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
        >&2 echo "ERROR WITH $PROFILE CREDENTIALS - EXITING!"
        EXITCODE=2
        exit $EXITCODE
      fi
      echo ""
    fi
  fi
}

printColorsCode(){
  if [[ $MONOCHROME -eq 0 ]]; then
    echo -e "\nColors Code for results: $NOTICE INFORMATIVE$NORMAL,$OK OK (RECOMMENDED VALUE)$NORMAL, $BAD WARNING (FIX REQUIRED)$NORMAL  \n"
  fi
}

# Generate Credential Report
genCredReport() {
  textTitle "0.1" "Generating AWS IAM Credential Report..." "NOT_SCORED" "SUPPORT"
  until $( $AWSCLI iam generate-credential-report --output text --query 'State' $PROFILE_OPT --region $REGION |grep -q -m 1 "COMPLETE") ; do
    sleep 1
  done
}

# Save report to a file, decode it, deletion at finish and after every single check
saveReport(){
  $AWSCLI iam get-credential-report --query 'Content' --output text $PROFILE_OPT --region $REGION | decode_report > $TEMP_REPORT_FILE
  if [[ $KEEPCREDREPORT -eq 1 ]]; then
    textTitle "0.2" "Saving IAM Credential Report ..." "NOT_SCORED" "SUPPORT"
    textNotice "IAM Credential Report saved in $TEMP_REPORT_FILE"
  fi
}

# Delete temporary report file
cleanTemp(){
  if [[ $KEEPCREDREPORT -ne 1 ]]; then
    rm -fr $TEMP_REPORT_FILE
  fi
}

# Delete the temporary report file if we get interrupted/terminated
trap cleanTemp EXIT

# Get a list of all available AWS Regions
REGIONS=$($AWSCLI ec2 describe-regions --query 'Regions[].RegionName' \
  --output text \
  $PROFILE_OPT \
  --region $REGION \
  --region-names $FILTERREGION)

infoReferenceLong(){
  # Report review note:
  echo -e ""
  echo -e "For more information on the Prowler, feedback and issue reporting:"
  echo -e "https://github.com/Alfresco/prowler"
  echo -e ""
  echo -e "For more information on the CIS benchmark:"
  echo -e "https://benchmarks.cisecurity.org/tools2/amazon/CIS_Amazon_Web_Services_Foundations_Benchmark_v1.1.0.pdf"

}

check11(){
  # "Avoid the use of the root account (Scored)."
  COMMAND11=$(cat $TEMP_REPORT_FILE| grep '<root_account>' | cut -d, -f5,11,16 | sed 's/,/\ /g')
  textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1"
  textNotice "Root account last accessed (password key_1 key_2): $COMMAND11"
}

check12(){
  # "Ensure multi-factor authentication (MFA) is enabled for all IAM users that have a console password (Scored)"
  # List users with password enabled
  COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }')
  COMMAND12=$(
    for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do
      cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$8 }' |grep "$i " |grep false | awk '{ print $1 }'
    done)
  textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1"
    if [[ $COMMAND12 ]]; then
      for u in $COMMAND12; do
        textWarn "User $u has Password enabled but MFA disabled"
      done
    else
      textOK "No users found with Password enabled and MFA disabled"
    fi
}

check13(){
  # "Ensure credentials unused for 90 days or greater are disabled (Scored)"
  textTitle "$ID13" "$TITLE13" "SCORED" "LEVEL1"
  #echo "$TITLE13"
  COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED=$(cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$4 }' |grep true | awk '{ print $1 }')
  if [[ $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED ]]; then
    COMMAND13=$(
    for i in $COMMAND12_LIST_USERS_WITH_PASSWORD_ENABLED; do
      cat $TEMP_REPORT_FILE|awk -F, '{ print $1,$5 }' |grep $i| awk '{ print $1 }'|tr '\n' ' ';
    done)
    # list of users that have used password
    USERS_PASSWORD_USED=$($AWSCLI iam list-users --query "Users[?PasswordLastUsed].UserName" --output text $PROFILE_OPT --region $REGION)
    if [[ $USERS_PASSWORD_USED ]]; then
      # look for users with a password last used more or equal to 90 days
      for i in $USERS_PASSWORD_USED; do
        DATEUSED=$($AWSCLI iam list-users --query "Users[?UserName=='$i'].PasswordLastUsed" --output text $PROFILE_OPT --region $REGION | cut -d'T' -f1)
        HOWOLDER=$(how_older_from_today $DATEUSED)
        if [ $HOWOLDER -gt "90" ];then
          textWarn "User '$i' has not logged in during the last 90 days "
        else
          textOK "User '$i' found with credentials used in the last 90 days"
        fi
      done
    fi
  else
      textOK "No users found with password enabled"
  fi

}

check14(){
  # "Ensure access keys are rotated every 90 days or less (Scored)" # also checked by Security Monkey
  LIST_OF_USERS_WITH_ACCESS_KEY1=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $9 }' |grep "\ true" | awk '{ print $1 }')
  LIST_OF_USERS_WITH_ACCESS_KEY2=$(cat $TEMP_REPORT_FILE| awk -F, '{ print $1, $14 }' |grep "\ true" | awk '{ print $1 }')
  textTitle "$ID14" "$TITLE14" "SCORED" "LEVEL1"
  C14_NUM_USERS1=0
  C14_NUM_USERS2=0
    if [[ $LIST_OF_USERS_WITH_ACCESS_KEY1 ]]; then
      # textWarn "Users with access key 1 older than 90 days:"
      for user in $LIST_OF_USERS_WITH_ACCESS_KEY1; do
        # check access key 1
        DATEROTATED1=$(cat $TEMP_REPORT_FILE | grep -v user_creation_time | grep "^${user},"| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }')
        HOWOLDER=$(how_older_from_today $DATEROTATED1)

        if [ $HOWOLDER -gt "90" ];then
          textWarn " $user has not rotated access key1 in over 90 days "
          C14_NUM_USERS1=$(expr $C14_NUM_USERS1 + 1)
        fi
      done
      if [[ $C14_NUM_USERS1 -eq 0 ]]; then
        textOK "No users with access key 1 older than 90 days."
      fi
    else
      textOK "No users with access key 1."
    fi

    if [[ $LIST_OF_USERS_WITH_ACCESS_KEY2 ]]; then
      # textWarn "Users with access key 2 older than 90 days:"
      for user in $LIST_OF_USERS_WITH_ACCESS_KEY2; do
        # check access key 2
        DATEROTATED2=$(cat $TEMP_REPORT_FILE | grep -v user_creation_time | grep "^${user},"| awk -F, '{ print $10 }' | grep -v "N/A" | awk -F"T" '{ print $1 }')
        HOWOLDER=$(how_older_from_today $DATEROTATED2)
        if [ $HOWOLDER -gt "90" ];then
          textWarn " $user has not rotated access key2. "
          C14_NUM_USERS2=$(expr $C14_NUM_USERS2 + 1)
        fi
      done
      if [[ $C14_NUM_USERS2 -eq 0 ]]; then
        textOK "No users with access key 2 older than 90 days."
      fi
    else
      textOK "No users with access key 2."
    fi
}

check15(){
  # "Ensure IAM password policy requires at least one uppercase letter (Scored)"
  COMMAND15=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireUppercaseCharacters' 2> /dev/null) # must be true
  textTitle "$ID15" "$TITLE15" "SCORED" "LEVEL1"
  if [[ "$COMMAND15" == "true" ]];then
    textOK "Password Policy requires upper case"
  else
    textWarn "Password Policy missing upper-case requirement"
  fi
}

check16(){
  # "Ensure IAM password policy require at least one lowercase letter (Scored)"
  COMMAND16=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireLowercaseCharacters' 2> /dev/null) # must be true
  textTitle "$ID16" "$TITLE16" "SCORED" "LEVEL1"
  if [[ "$COMMAND16" == "true" ]];then
    textOK "Password Policy requires lower case"
  else
    textWarn "Password Policy missing lower-case requirement"
  fi
}

check17(){
  # "Ensure IAM password policy require at least one symbol (Scored)"
  COMMAND17=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireSymbols' 2> /dev/null) # must be true
  textTitle "$ID17" "$TITLE17" "SCORED" "LEVEL1"
  if [[ "$COMMAND17" == "true" ]];then
    textOK "Password Policy requires symbol"
  else
    textWarn "Password Policy missing symbol requirement"
  fi
}

check18(){
  # "Ensure IAM password policy require at least one number (Scored)"
  COMMAND18=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.RequireNumbers' 2> /dev/null) # must be true
  textTitle "$ID18" "$TITLE18" "SCORED" "LEVEL1"
  if [[ "$COMMAND18" == "true" ]];then
    textOK "Password Policy requires number"
  else
    textWarn "Password Policy missing number requirement"
  fi
}

check19(){
  # "Ensure IAM password policy requires minimum length of 14 or greater (Scored)"
  COMMAND19=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json --query 'PasswordPolicy.MinimumPasswordLength' 2> /dev/null)
  textTitle "$ID19" "$TITLE19" "SCORED" "LEVEL1"
  if [[ $COMMAND19 -gt "13" ]];then
    textOK "Password Policy requires more than 13 characters"
  else
    textWarn "Password Policy missing or weak length requirement"
  fi
}

check110(){
  # "Ensure IAM password policy prevents password reuse: 24 or greater (Scored)"
  COMMAND110=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --query 'PasswordPolicy.PasswordReusePrevention' --output text 2> /dev/null)
  textTitle "$ID110" "$TITLE110" "SCORED" "LEVEL1"
  if [[ $COMMAND110 ]];then
    if [[ $COMMAND110 -gt "23" ]];then
      textOK "Password Policy limits reuse"
    else
      textWarn "Password Policy has weak reuse requirement (lower than 24)"
    fi
  else
    textWarn "Password Policy missing reuse requirement"
  fi
}

check111(){
  # "Ensure IAM password policy expires passwords within 90 days or less (Scored)"
  COMMAND111=$($AWSCLI iam get-account-password-policy $PROFILE_OPT --region $REGION --output json | grep MaxPasswordAge | awk -F: '{ print $2 }'|sed 's/\ //g'|sed 's/,/ /g' 2> /dev/null)
  textTitle "$ID111" "$TITLE111" "SCORED" "LEVEL1"
  if [[ $COMMAND111 ]];then
    if [ "$COMMAND111" == "90" ];then
      textOK "Password Policy includes expiration"
    fi
  else
    textWarn "Password expiration not set or set greater than 90 days "
  fi
}

check112(){
  # "Ensure no root account access key exists (Scored)"
  # ensure the access_key_1_active and access_key_2_active fields are set to FALSE.
  ROOTKEY1=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $9 }')
  ROOTKEY2=$(cat $TEMP_REPORT_FILE |grep root_account|awk -F',' '{ print $14 }')
  textTitle "$ID112" "$TITLE112" "SCORED" "LEVEL1"
  if [ "$ROOTKEY1" == "false" ];then
    textOK "No access key 1 found for root"
  else
    textWarn "Found access key 1 for root "
  fi
  if [ "$ROOTKEY2" == "false" ];then
    textOK "No access key 2 found for root"
  else
    textWarn "Found access key 2 for root "
  fi
}

check113(){
  # "Ensure MFA is enabled for the root account (Scored)"
  COMMAND113=$($AWSCLI iam get-account-summary $PROFILE_OPT --region $REGION --output json --query 'SummaryMap.AccountMFAEnabled')
  textTitle "$ID113" "$TITLE113" "SCORED" "LEVEL1"
  if [ "$COMMAND113" == "1" ]; then
    textOK "Virtual MFA is enabled for root"
  else
    textWarn "MFA is not ENABLED for root account "
  fi
}

check114(){
  # "Ensure hardware MFA is enabled for the root account (Scored)"
  COMMAND113=$($AWSCLI iam get-account-summary $PROFILE_OPT --region $REGION --output json --query 'SummaryMap.AccountMFAEnabled')
  textTitle "$ID114" "$TITLE114" "SCORED" "LEVEL1"
  if [ "$COMMAND113" == "1" ]; then
    COMMAND114=$($AWSCLI iam list-virtual-mfa-devices $PROFILE_OPT --region $REGION --output text --assignment-status Assigned --query 'VirtualMFADevices[*].[SerialNumber]' | grep '^arn:aws:iam::[0-9]\{12\}:mfa/root-account-mfa-device$')
    if [[ "$COMMAND114" ]]; then
      textWarn "Only Virtual MFA is enabled for root"
    else
      textOK "Hardware MFA is enabled for root "
    fi
  else
    textWarn "MFA is not ENABLED for root account "
  fi
}

check115(){
  # "Ensure security questions are registered in the AWS account (Not Scored)"
  textTitle "$ID115" "$TITLE115" "NOT_SCORED" "LEVEL2"
  textNotice "No command available for check 1.15 "
  textNotice "Login to the AWS Console as root & click on the Account "
  textNotice "Name -> My Account -> Configure Security Challenge Questions "
}

check116(){
  # "Ensure IAM policies are attached only to groups or roles (Scored)"
  textTitle "$ID116" "$TITLE116" "SCORED" "LEVEL1"
  LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text $PROFILE_OPT --region $REGION)
  C116_NUM_USERS=0
  for user in $LIST_USERS;do
    USER_POLICY=$($AWSCLI iam list-attached-user-policies --output text $PROFILE_OPT --region $REGION --user-name $user)
    if [[ $USER_POLICY ]]; then
      textWarn "$user has policy directly attached "
      C116_NUM_USERS=$(expr $C116_NUM_USERS + 1)
    fi
  done
  if [[ $C116_NUM_USERS -eq 0 ]]; then
    textOK "No policies attached to users."
  fi
}

check117(){
  # "Enable detailed billing (Scored)"
  # No command available
  textTitle "$ID117" "$TITLE117" "SCORED" "LEVEL1"
  textNotice "No command available for check 1.17 "
  textNotice "See section 1.17 on the CIS Benchmark guide for details "
}

check118(){
  # "Ensure IAM Master and IAM Manager roles are active (Scored)"
  textTitle "$ID118" "$TITLE118" "SCORED" "LEVEL1"
  FINDMASTERANDMANAGER=$($AWSCLI iam list-roles $PROFILE_OPT --region $REGION --query "Roles[*].{RoleName:RoleName}" --output text | grep -E 'Master|Manager'| tr '\n' ' ')
  if [[ $FINDMASTERANDMANAGER ]];then
    textNotice "Found next roles as possible IAM Master and IAM Manager candidates: "
    textNotice "$FINDMASTERANDMANAGER "
    textNotice "run the commands below to check their policies with section 1.18 in the guide..."
    for role in $FINDMASTERANDMANAGER;do
      # find inline policies in found roles
      INLINEPOLICIES=$($AWSCLI iam list-role-policies --role-name $role $PROFILE_OPT --region $REGION --query "PolicyNames[*]" --output text)
      for policy in $INLINEPOLICIES;do
        textNotice "INLINE: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy $PROFILE_OPT --region $REGION --output json"
      done
      # find attached policies in found roles
      ATTACHEDPOLICIES=$($AWSCLI iam list-attached-role-policies --role-name $role $PROFILE_OPT --region $REGION --query "AttachedPolicies[*]" --output text)
      for policy in $ATTACHEDPOLICIES;do
        textNotice "ATTACHED: $AWSCLI iam get-role-policy --role-name $role --policy-name $policy $PROFILE_OPT --region $REGION --output json"
      done
    done
  else
    textWarn "IAM Master and IAM Manager roles not found"
  fi
}

check119(){
  # "Maintain current contact details (Scored)"
  # No command available
  textTitle "$ID119" "$TITLE119" "SCORED" "LEVEL1"
  textNotice "No command available for check 1.19 "
  textNotice "See section 1.19 on the CIS Benchmark guide for details "
}

check120(){
  # "Ensure security contact information is registered (Scored)"
  # No command available
  textTitle "$ID120" "$TITLE120" "SCORED" "LEVEL1"
  textNotice "No command available for check 1.20 "
  textNotice "See section 1.20 on the CIS Benchmark guide for details "
}

check121(){
  # "Ensure IAM instance roles are used for AWS resource access from instances (Not Scored)"
  textTitle "$ID121" "$TITLE121" "NOT_SCORED" "LEVEL2"
  textNotice "No command available for check 1.21 "
  textNotice "See section 1.21 on the CIS Benchmark guide for details "
}

check122(){
  # "Ensure a support role has been created to manage incidents with AWS Support (Scored)"
  textTitle "$ID122" "$TITLE122" "SCORED" "LEVEL1"
  SUPPORTPOLICYARN=$($AWSCLI iam list-policies --query "Policies[?PolicyName == 'AWSSupportAccess'].Arn" $PROFILE_OPT --region $REGION --output text)
  if [[ $SUPPORTPOLICYARN ]];then
    for policyarn in $SUPPORTPOLICYARN;do
      POLICYUSERS=$($AWSCLI iam list-entities-for-policy --policy-arn $SUPPORTPOLICYARN $PROFILE_OPT --region $REGION --output json)
      if [[ $POLICYUSERS ]];then
        textOK "Support Policy attached to $policyarn"
        for user in $(echo "$POLICYUSERS" | grep UserName | cut -d'"' -f4) ; do
          textNotice "User $user has support access via $policyarn"
        done
        # textNotice "Make sure your team can create a Support case with AWS "
      else
        textWarn "Support Policy not applied to any Group / User / Role "
      fi
    done
  else
    textWarn "No Support Policy found"
  fi
}

check123(){
  # "Do not setup access keys during initial user setup for all IAM users that have a console password (Not Scored)"
  textTitle "$ID123" "$TITLE123" "NOT_SCORED" "LEVEL1"
  LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text $PROFILE_OPT --region $REGION)
  # List of USERS with KEY1 last_used_date as N/A
  LIST_USERS_KEY1_NA=$(for user in $LIST_USERS; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$11 }'|grep N/A |awk '{ print $1 }'; done)
  LIST_USERS_KEY1_ACTIVE=$(for user in $LIST_USERS_KEY1_NA; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$9 }'|grep "true$"|awk '{ print $1 }'|sed 's/[[:blank:]]+/,/g' ; done)
  if [[ $LIST_USERS_KEY1_ACTIVE ]]; then
    for user in $LIST_USERS_KEY1_ACTIVE; do
      textNotice "$user has never used Access Key 1"
    done
  else
    textOK "No users found with Access Key 1 never used"
  fi
  # List of USERS with KEY2 last_used_date as N/A
  LIST_USERS_KEY2_NA=$(for user in $LIST_USERS; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$16 }'|grep N/A |awk '{ print $1 }' ; done)
  LIST_USERS_KEY2_ACTIVE=$(for user in $LIST_USERS_KEY2_NA; do grep "^${user}," $TEMP_REPORT_FILE|awk -F, '{ print $1,$14 }'|grep "true$" |awk '{ print $1 }' ; done)
  if [[ $LIST_USERS_KEY2_ACTIVE ]]; then
    for user in $LIST_USERS_KEY2_ACTIVE; do
      textNotice "$user has never used Access Key 2"
    done
  else
    textOK "No users found with Access Key 2 never used"
  fi
}

check124(){
  # "Ensure IAM policies that allow full \"*:*\" administrative privileges are not created (Scored)"
  textTitle "$ID124" "$TITLE124" "SCORED" "LEVEL1"
  LIST_CUSTOM_POLICIES=$($AWSCLI iam list-policies --output text $PROFILE_OPT --region $REGION|grep 'arn:aws:iam::[0-9]\{12\}:'|awk '{ print $2 }')
  if [[ $LIST_CUSTOM_POLICIES ]]; then
    textNotice "Looking for custom policies: (skipping default policies - it may take few seconds...)"
    for policy in $LIST_CUSTOM_POLICIES; do
      POLICY_VERSION=$($AWSCLI iam list-policies $PROFILE_OPT --region $REGION --query 'Policies[*].[Arn,DefaultVersionId]' --output text |awk "\$1 == \"$policy\" { print \$2 }")
      POLICY_WITH_FULL=$($AWSCLI iam get-policy-version --output text --policy-arn $policy --version-id $POLICY_VERSION --query "PolicyVersion.Document.Statement[?Effect == 'Allow' && contains(Resource, '*') && contains (Action, '*')]" $PROFILE_OPT --region $REGION)
      if [[ $POLICY_WITH_FULL ]]; then
        POLICIES_ALLOW_LIST="$POLICIES_ALLOW_LIST $policy"
      fi
    done
    if [[ $POLICIES_ALLOW_LIST ]]; then
      textNotice "List of custom policies: "
      for policy in $POLICIES_ALLOW_LIST; do
        textNotice "Policy $policy allows \"*:*\""
      done
    else
        textOK "No custom policy found that allow full \"*:*\" administrative privileges"
    fi
  else
    textOK "No custom policies found"
  fi
}

check21(){
  # "Ensure CloudTrail is enabled in all regions (Scored)"
  textTitle "$ID21" "$TITLE21" "SCORED" "LEVEL1"
  LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].Name' --output text)
  if [[ $LIST_OF_TRAILS ]];then
    for trail in $LIST_OF_TRAILS;do
      MULTIREGION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].IsMultiRegionTrail' --output text --trail-name-list $trail)
      if [[ "$MULTIREGION_TRAIL_STATUS" == 'False' ]];then
        textWarn "$trail trail in $REGION is not enabled in multi region mode"
      else
        textOK "$trail trail in $REGION is enabled for all regions"
      fi
    done
  else
    textWarn "No CloudTrail trails found!"
  fi
}

check22(){
  # "Ensure CloudTrail log file validation is enabled (Scored)"
  textTitle "$ID22" "$TITLE22" "SCORED" "LEVEL2"
  LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].Name' --output text)
  if [[ $LIST_OF_TRAILS ]];then
    for trail in $LIST_OF_TRAILS;do
      LOGFILEVALIDATION_TRAIL_STATUS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].LogFileValidationEnabled' --output text --trail-name-list $trail)
      if [[ "$LOGFILEVALIDATION_TRAIL_STATUS" == 'False' ]];then
        textWarn "$trail trail in $REGION has not log file validation enabled"
      else
        textOK "$trail trail in $REGION has log file validation enabled"
      fi
    done
  else
    textWarn "No CloudTrail trails found!"
  fi
}

check23(){
  # "Ensure the S3 bucket CloudTrail logs to is not publicly accessible (Scored)"
  textTitle "$ID23" "$TITLE23" "SCORED" "LEVEL1"
  CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text $PROFILE_OPT --region $REGION)
  if [[ $CLOUDTRAILBUCKET ]];then
    for bucket in $CLOUDTRAILBUCKET;do
      CLOUDTRAILBUCKET_HASALLPERMISIONS=$($AWSCLI s3api get-bucket-acl --bucket $bucket --query 'Grants[?Grantee.URI==`http://acs.amazonaws.com/groups/global/AllUsers`]' $PROFILE_OPT --region $REGION --output text)
      if [[ $CLOUDTRAILBUCKET_HASALLPERMISIONS ]];then
        textWarn "check your $bucket CloudTrail bucket ACL and Policy!"
      else
        textOK "Bucket $bucket is set correctly"
      fi
    done
  else
    textWarn "No CloudTrail bucket found!"
  fi
}

check24(){
  # "Ensure CloudTrail trails are integrated with CloudWatch Logs (Scored)"
  textTitle "$ID24" "$TITLE24" "SCORED" "LEVEL1"
  TRAILS_AND_REGIONS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].{Name:Name, HomeRegion:HomeRegion}' --output text | tr "\t" ',')
  if [[ $TRAILS_AND_REGIONS ]];then
    for reg_trail in $TRAILS_AND_REGIONS;do
      trail=$(echo $reg_trail | cut -d',' -f2)
      TRAIL_REGION=$(echo $reg_trail | cut -d',' -f1)
      LATESTDELIVERY_TIMESTAMP=$($AWSCLI cloudtrail get-trail-status --name $trail $PROFILE_OPT --region $TRAIL_REGION --query 'LatestCloudWatchLogsDeliveryTime' --output text|grep -v None)
      if [[ ! $LATESTDELIVERY_TIMESTAMP ]];then
        textWarn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)"
      else
        LATESTDELIVERY_DATE=$(timestamp_to_date $LATESTDELIVERY_TIMESTAMP)
        HOWOLDER=$(how_older_from_today $LATESTDELIVERY_DATE)
        if [ $HOWOLDER -gt "1" ];then
          textWarn "$trail trail is not logging in the last 24h or not configured (it is in $TRAIL_REGION)"
        else
          textOK "$trail trail has been logging during the last 24h (it is in $TRAIL_REGION)"
        fi
      fi
    done
  else
    textWarn "No CloudTrail trails found!"
  fi
}

check25(){
  # "Ensure AWS Config is enabled in all regions (Scored)"
  textTitle "$ID25" "$TITLE25" "SCORED" "LEVEL1"
  for regx in $REGIONS; do
    CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice get-status $PROFILE_OPT --region $regx --output json| grep "recorder: ON")
    if [[ $CHECK_AWSCONFIG_STATUS ]];then
      textOK "Region $regx has AWS Config recorder: ON" "$regx"
    else
      textWarn "Region $regx has AWS Config disabled or not configured" "$regx"
    fi
  done
}

check26(){
  # "Ensure S3 bucket access logging is enabled on the CloudTrail S3 bucket (Scored)"
  textTitle "$ID26" "$TITLE26" "SCORED" "LEVEL1"
  CLOUDTRAILBUCKET=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].S3BucketName' --output text $PROFILE_OPT --region $REGION)
    if [[ $CLOUDTRAILBUCKET ]];then
      for bucket in $CLOUDTRAILBUCKET;do
        CLOUDTRAILBUCKET_LOGENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket $PROFILE_OPT --region $REGION --query 'LoggingEnabled.TargetBucket' --output text|grep -v None)
        if [[ $CLOUDTRAILBUCKET_LOGENABLED ]];then
          textOK "Bucket access logging enabled in $bucket"
        else
          textWarn "access logging is not enabled in $bucket CloudTrail S3 bucket!"
        fi
      done
    else
      textWarn "CloudTrail bucket not found!"
    fi
}

check27(){
  # "Ensure CloudTrail logs are encrypted at rest using KMS CMKs (Scored)"
  textTitle "$ID27" "$TITLE27" "SCORED" "LEVEL2"
  CLOUDTRAILNAME=$($AWSCLI cloudtrail describe-trails --query 'trailList[*].Name' --output text $PROFILE_OPT --region $REGION)
    if [[ $CLOUDTRAILNAME ]];then
      for trail in $CLOUDTRAILNAME;do
        CLOUDTRAILENC_ENABLED=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --trail $trail --query 'trailList[*].KmsKeyId' --output text)
        if [[ $CLOUDTRAILENC_ENABLED ]];then
          textOK "KMS key found for $trail"
        else
          textWarn "encryption is not enabled in your CloudTrail trail $trail (KMS key not found)!"
        fi
      done
    else
      textWarn "CloudTrail bucket doesn't exist!"
    fi
}

check28(){
  # "Ensure rotation for customer created CMKs is enabled (Scored)"
  textTitle "$ID28" "$TITLE28" "SCORED" "LEVEL2"
  for regx in $REGIONS; do
  CHECK_KMS_KEYLIST=$($AWSCLI kms list-keys $PROFILE_OPT --region $regx --output text --query 'Keys[*].KeyId')
    if [[ $CHECK_KMS_KEYLIST ]];then
      CHECK_KMS_KEYLIST_NO_DEFAULT=$(for key in $CHECK_KMS_KEYLIST ; do $AWSCLI kms describe-key --key-id $key $PROFILE_OPT --region $regx --output text|grep -v 'Default master key that protects my ACM private keys when no other key is defined'|awk '{ print $3 }'|awk -F'/' '{ print $2 }'; done)
      for key in $CHECK_KMS_KEYLIST_NO_DEFAULT; do
        CHECK_KMS_KEY_TYPE=$($AWSCLI kms describe-key --key-id $key $PROFILE_OPT --region $regx --query 'KeyMetadata.Origin' | sed 's/["]//g')
          if [[ "$CHECK_KMS_KEY_TYPE" == "EXTERNAL" ]];then
            textOK "Key $key in Region $regx Customer Uploaded Key Material." "$regx"
          else
            CHECK_KMS_KEY_ROTATION=$($AWSCLI kms get-key-rotation-status --key-id $key $PROFILE_OPT --region $regx --output text)
            #CHECK_KMS_DEFAULT_KEY=$($AWSCLI kms describe-key --key-id $key $PROFILE_OPT --region $regx --query 'KeyMetadata.Description' | sed -n '/Default master key that protects my ACM private keys when no other key is defined /p'|| echo "False")
              if [[ "$CHECK_KMS_KEY_ROTATION" == "True" ]];then
                    textOK "Key $key in Region $regx is set correctly"
                  elif [[ "$CHECK_KMS_KEY_ROTATION" == "False" && $CHECK_KMS_DEFAULT_KEY ]];then
                textNotice "Region $regx key $key is an AWS default master key and cannot be deleted nor modified." "$regx"
              else
                textWarn "Key $key in Region $regx is not set to rotate!!!" "$regx"
              fi
          fi
      done

  else
      textNotice "Region $regx doesn't have encryption keys" "$regx"
  fi
  done
}

check31(){
  # "Ensure a log metric filter and alarm exist for unauthorized API calls (Scored)"
  textTitle "$ID31" "$TITLE31" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text| tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | grep $group | awk -F: '{ print $4 }')
      #METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | awk '/UnauthorizedOperation/ || /AccessDenied/ {print $3}')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --output text | grep METRICFILTERS | awk 'BEGIN {IGNORECASE=1}; /UnauthorizedOperation/ || /AccessDenied/ {print $3};')
      if [[ $METRICFILTER_SET ]];then
        for metric in $METRICFILTER_SET; do
          metric_name=$($AWSCLI logs describe-metric-filters $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --log-group-name $group --filter-name-prefix $metric --output text --query 'metricFilters[0].metricTransformations[0].metricName')
          HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[?MetricName==`'$metric_name'`]' --output text)
          if [[ $HAS_ALARM_ASSOCIATED ]];then
            CHECK31OK="$CHECK31OK $group:$metric"
          else
            CHECK31WARN="$CHECK31WARN $group:$metric"
          fi
        done
      else
        CHECK31WARN="$CHECK31WARN $group"
      fi
    done

    if [[ $CHECK31OK ]]; then
      for group in $CHECK31OK; do
        metric=${group#*:}
        group=${group%:*}
        textOK "CloudWatch group $group found with metric filter $metric and alarms set for Unauthorized Operation and Access Denied"
      done
    fi
    if [[ $CHECK31WARN ]]; then
      for group in $CHECK31WARN; do
        case $group in
           *:*) metric=${group#*:}
                group=${group%:*}
                textWarn "CloudWatch group $group found with metric filter $metric but no alarms associated"
                ;;
             *) textWarn "CloudWatch group $group found but no metric filters or alarms associated"
        esac
      done
    fi
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check32(){
  # "Ensure a log metric filter and alarm exist for Management Console sign-in without MFA (Scored)"
  textTitle "$ID32" "$TITLE32" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' |grep filterPattern|grep MFAUsed| awk '/ConsoleLogin/ && (/additionalEventData.MFAUsed.*\!=.*\"Yes/) {print $1}')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /ConsoleLogin/ || /MFAUsed/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms set for sign-in Console without MFA enabled"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check33(){
  # "Ensure a log metric filter and alarm exist for usage of root account (Scored)"
  textTitle "$ID33" "$TITLE33" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION |grep -E 'userIdentity.*Root.*AwsServiceEvent')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /userIdentity/ || /Root/ || /AwsServiceEvent/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms set for usage of root account"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check34(){
  # "Ensure a log metric filter and alarm exist for IAM policy changes (Scored)"
  textTitle "$ID34" "$TITLE34" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'DeleteGroupPolicy.*DeleteRolePolicy.*DeleteUserPolicy.*PutGroupPolicy.*PutRolePolicy.*PutUserPolicy.*CreatePolicy.*DeletePolicy.*CreatePolicyVersion.*DeletePolicyVersion.*AttachRolePolicy.*DetachRolePolicy.*AttachUserPolicy.*DetachUserPolicy.*AttachGroupPolicy.*DetachGroupPolicy')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /DeletePolicy/ || /DeletePolicies/ || /Policies/ || /Policy/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for IAM policy changes"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check35(){
  # "Ensure a log metric filter and alarm exist for CloudTrail configuration changes (Scored)"
  textTitle "$ID35" "$TITLE35" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateTrail.*UpdateTrail.*DeleteTrail.*StartLogging.*StopLogging')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /TrailChange/ || /Trails/ || /CreateTrail/ || /UpdateTrail/ || /DeleteTrail/ || /StartLogging/ || /StopLogging/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for CloudTrail configuration changes"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check36(){
  # "Ensure a log metric filter and alarm exist for AWS Management Console authentication failures (Scored)"
  textTitle "$ID36" "$TITLE36" "SCORED" "LEVEL2"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'ConsoleLogin.*Failed')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /FailedLogin/ || /ConsoleLogin/ || /Failed/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for AWS Management Console authentication failures"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check37(){
  # "Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs (Scored)"
  textTitle "$ID37" "$TITLE37" "SCORED" "LEVEL2"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'kms.amazonaws.com.*DisableKey.*ScheduleKeyDeletion')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /DisableKey/ || /ScheduleKeyDeletion/ || /kms/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for changes of customer created CMKs"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check38(){
  # "Ensure a log metric filter and alarm exist for S3 bucket policy changes (Scored)"
  textTitle "$ID38" "$TITLE38" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 's3.amazonaws.com.*PutBucketAcl.*PutBucketPolicy.*PutBucketCors.*PutBucketLifecycle.*PutBucketReplication.*DeleteBucketPolicy.*DeleteBucketCors.*DeleteBucketLifecycle.*DeleteBucketReplication')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /S3/ || /BucketPolicy/ || /BucketPolicies/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for S3 bucket policy changes"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check39(){
  # "Ensure a log metric filter and alarm exist for AWS Config configuration changes (Scored)"
  textTitle "$ID39" "$TITLE39" "SCORED" "LEVEL2"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'config.amazonaws.com.*StopConfigurationRecorder.*DeleteDeliveryChannel.*PutDeliveryChannel.*PutConfigurationRecorder')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /config/ || /ConfigurationRecorder/ || /DeliveryChannel/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for AWS Config configuration changes"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check310(){
  # "Ensure a log metric filter and alarm exist for security group changes (Scored)"
  textTitle "$ID310" "$TITLE310" "SCORED" "LEVEL2"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'AuthorizeSecurityGroupIngress.*AuthorizeSecurityGroupEgress.*RevokeSecurityGroupIngress.*RevokeSecurityGroupEgress.*CreateSecurityGroup.*DeleteSecurityGroup')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /SecurityGroup/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for security group changes"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check311(){
  # "Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL) (Scored)"
  textTitle "$ID311" "$TITLE311" "SCORED" "LEVEL2"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateNetworkAcl.*CreateNetworkAclEntry.*DeleteNetworkAcl.*DeleteNetworkAclEntry.*ReplaceNetworkAclEntry.*ReplaceNetworkAclAssociation')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /NetworkAcl/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for changes to NACLs"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check312(){
  # "Ensure a log metric filter and alarm exist for changes to network gateways (Scored)"
  textTitle "$ID312" "$TITLE312" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateCustomerGateway.*DeleteCustomerGateway.*AttachInternetGateway.*CreateInternetGateway.*DeleteInternetGateway.*DetachInternetGateway')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /InternetGateway/ || /CustomerGateway/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for changes to network gateways"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check313(){
  # "Ensure a log metric filter and alarm exist for route table changes (Scored)"
  textTitle "$ID313" "$TITLE313" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateRoute.*CreateRouteTable.*ReplaceRoute.*ReplaceRouteTableAssociation.*DeleteRouteTable.*DeleteRoute.*DisassociateRouteTable')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /Route/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for route table changes"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check314(){
  # "Ensure a log metric filter and alarm exist for VPC changes (Scored)"
  textTitle "$ID314" "$TITLE314" "SCORED" "LEVEL1"
  CLOUDWATCH_GROUP=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | tr '\011' '\012' | awk -F: '{ print $7 }')
  if [[ $CLOUDWATCH_GROUP ]];then
    for group in $CLOUDWATCH_GROUP; do
      CLOUDWATCH_LOGGROUP_REGION=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $REGION --query 'trailList[*].CloudWatchLogsLogGroupArn' --output text | awk -F: '{ print $4 }')
      METRICFILTER_SET=$($AWSCLI logs describe-metric-filters --log-group-name $group $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'metricFilters' | grep -E 'CreateVpc.*DeleteVpc.*ModifyVpcAttribute.*AcceptVpcPeeringConnection.*CreateVpcPeeringConnection.*DeleteVpcPeeringConnection.*RejectVpcPeeringConnection.*AttachClassicLinkVpc.*DetachClassicLinkVpc.*DisableVpcClassicLink.*EnableVpcClassicLink')
      if [[ $METRICFILTER_SET ]];then
        HAS_ALARM_ASSOCIATED=$($AWSCLI cloudwatch describe-alarms $PROFILE_OPT --region $CLOUDWATCH_LOGGROUP_REGION --query 'MetricAlarms[].MetricName' --output text | awk 'BEGIN {IGNORECASE=1}; /VPC/;')
        if [[ $HAS_ALARM_ASSOCIATED ]];then
          textOK "CloudWatch group $group found with metric filters and alarms for VPC changes"
        else
          textWarn "CloudWatch group $group found with metric filters but no alarms associated"
        fi
      else
        textWarn "CloudWatch group $group found but no metric filters or alarms associated"
      fi
    done
  else
    textWarn "No CloudWatch group found for CloudTrail events"
  fi
}

check315(){
  # "Ensure appropriate subscribers to each SNS topic (Not Scored)"
  textTitle "$ID315" "$TITLE315" "NOT_SCORED" "LEVEL1"
  CAN_SNS_LIST_SUBS=1
  for regx in $REGIONS; do
    TOPICS_LIST=$($AWSCLI sns list-topics $PROFILE_OPT --region $regx --output text --query 'Topics[*].TopicArn')
    ntopics=$(echo $TOPICS_LIST | wc -w )
    if [[ $TOPICS_LIST && $CAN_SNS_LIST_SUBS -eq 1 ]];then
      textNotice "Region $regx has $ntopics topics" "$regx"
      for topic in $TOPICS_LIST; do
        TOPIC_SHORT=$(echo $topic | awk -F: '{ print $6 }')
        CHECK_TOPIC_LIST=$($AWSCLI sns list-subscriptions-by-topic --topic-arn $topic $PROFILE_OPT --region $regx --query 'Subscriptions[*].{Endpoint:Endpoint,Protocol:Protocol}' --output text --max-items $MAXITEMS 2> /dev/null)
        if [[ $? -eq 255 ]]; then
          # Permission error
          export CAN_SNS_LIST_SUBS=0
          ntopics=$(echo $TOPICS_LIST | wc -w )
          textNotice "Region $regx / $ntopics Topics / Subscriptions NO_PERMISSION" "$regx"
          break;
        fi
        if [[ "Z" != "Z${CHECK_TOPIC_LIST}" ]]; then
          printf '%s\n' "$CHECK_TOPIC_LIST" | while IFS= read -r dest ; do
            textNotice "Region $regx / Topic $TOPIC_SHORT / Subscription $dest" "$regx"
          done
        else
          textWarn "Region $regx / Topic $TOPIC_SHORT / Subscription NONE" "$regx"
        fi
      done
    elif [[ $CAN_SNS_LIST_SUBS -eq 0 ]]; then
      textNotice "Region $regx has $ntopics topics - unable to list subscribers" "$regx"
      # break
    else
      textOK "Region $regx has 0 topics" "$regx"
    fi
  done
}

check41(){
  # "Ensure no security groups allow ingress from 0.0.0.0/0 to port 22 (Scored)"
  textTitle "$ID41" "$TITLE41" "SCORED" "LEVEL1"
  for regx in $REGIONS; do
    SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=22" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`22` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupId:GroupId}' $PROFILE_OPT --region $regx --output text)
    if [[ $SG_LIST ]];then
      for SG in $SG_LIST;do
        textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx" "$regx"
      done
    else
      textOK "No Security Groups found in $regx with port 22 TCP open to 0.0.0.0/0" "$regx"
    fi
  done
}

check42(){
  # "Ensure no security groups allow ingress from 0.0.0.0/0 to port 3389 (Scored)"
  textTitle "$ID42" "$TITLE42" "SCORED" "LEVEL1"
  for regx in $REGIONS; do
    SG_LIST=$($AWSCLI ec2 describe-security-groups --filters "Name=ip-permission.to-port,Values=3389" --query 'SecurityGroups[?length(IpPermissions[?ToPort==`3389` && contains(IpRanges[].CidrIp, `0.0.0.0/0`)]) > `0`].{GroupName: GroupName}' $PROFILE_OPT --region $regx --output text)
    if [[ $SG_LIST ]];then
      for SG in $SG_LIST;do
        textWarn "Found Security Group: $SG open to 0.0.0.0/0 in Region $regx" "$regx"
      done
    else
      textOK "No Security Groups found in $regx with port 3389 TCP open to 0.0.0.0/0" "$regx"
    fi
  done
}

check43(){
  # "Ensure VPC Flow Logging is Enabled in all VPCs (Scored)"
  textTitle "$ID43" "$TITLE43" "SCORED" "LEVEL2"
  for regx in $REGIONS; do
    CHECK_FL=$($AWSCLI ec2 describe-flow-logs $PROFILE_OPT --region $regx --query 'FlowLogs[?FlowLogStatus==`ACTIVE`].LogGroupName' --output text)
    if [[ $CHECK_FL ]];then
      for FL in $CHECK_FL;do
        textOK "VPCFlowLog is enabled for LogGroupName: $FL in Region $regx" "$regx"
      done
    else
      textWarn "No VPCFlowLog has been found in Region $regx" "$regx"
    fi
  done
}

check44(){
  # "Ensure the default security group of every VPC restricts all traffic (Scored)"
  textTitle "$ID44" "$TITLE44" "SCORED" "LEVEL2"
  for regx in $REGIONS; do
    CHECK_SGDEFAULT=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --filters Name=group-name,Values='default' --query 'SecurityGroups[*].{IpPermissions:IpPermissions,IpPermissionsEgress:IpPermissionsEgress,GroupId:GroupId}' --output text |grep 0.0.0.0)
    if [[ $CHECK_SGDEFAULT ]];then
      textWarn "Default Security Groups found that allow 0.0.0.0 IN or OUT traffic in Region $regx" "$regx"
    else
      textOK "No Default Security Groups open to 0.0.0.0 found in Region $regx" "$regx"
    fi
  done
}

check45(){
  # "Ensure routing tables for VPC peering are \"least access\" (Not Scored)"
  textTitle "$ID45" "$TITLE45" "NOT_SCORED" "LEVEL2"
  textNotice "Looking for VPC peering in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_VPCS_PEERING_CONNECTIONS=$($AWSCLI ec2 describe-vpc-peering-connections --output text $PROFILE_OPT --region $regx --query 'VpcPeeringConnections[*].VpcPeeringConnectionId')
    if [[ $LIST_OF_VPCS_PEERING_CONNECTIONS ]];then
      textNotice "$regx: $LIST_OF_VPCS_PEERING_CONNECTIONS - review routing tables" "$regx"
      #LIST_OF_VPCS=$($AWSCLI ec2 describe-vpcs $PROFILE_OPT --region $regx --query 'Vpcs[*].VpcId' --output text)
      #aws ec2 describe-route-tables --filter "Name=vpc-id,Values=vpc-0213e864" --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" $PROFILE_OPT --region $regx
      # for vpc in $LIST_OF_VPCS; do
      #   VPCS_WITH_PEERING=$($AWSCLI ec2 describe-route-tables --filter "Name=vpc-id,Values=$vpc" $PROFILE_OPT --region $regx --query "RouteTables[*].{RouteTableId:RouteTableId, VpcId:VpcId, Routes:Routes, AssociatedSubnets:Associations[*].SubnetId}" |grep GatewayId|grep pcx-)
      # done
      #echo $VPCS_WITH_PEERING
    else
      textOK "$regx: No VPC peering found" "$regx"
    fi
  done
}

extra71(){
  # "Ensure users with AdministratorAccess policy have MFA tokens enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID71" "$TITLE71" "NOT_SCORED" "EXTRA"

  ADMIN_GROUPS=''
  AWS_GROUPS=$($AWSCLI $PROFILE_OPT iam list-groups --output text --query 'Groups[].GroupName')
  for grp in $AWS_GROUPS; do
    # aws --profile onlinetraining iam list-attached-group-policies --group-name Administrators --query 'AttachedPolicies[].PolicyArn' | grep 'arn:aws:iam::aws:policy/AdministratorAccess'
    # list-attached-group-policies
    CHECK_ADMIN_GROUP=$($AWSCLI $PROFILE_OPT iam list-attached-group-policies --group-name $grp --output json --query 'AttachedPolicies[].PolicyArn' | grep  'arn:aws:iam::aws:policy/AdministratorAccess')
    if [[ $CHECK_ADMIN_GROUP ]]; then
      ADMIN_GROUPS="$ADMIN_GROUPS $grp"
      textNotice "$grp group provides administrative access"
      ADMIN_USERS=$($AWSCLI $PROFILE_OPT iam get-group --group-name $grp --output json --query 'Users[].UserName' | grep '"' | cut -d'"' -f2 )
      for auser in $ADMIN_USERS; do
        # users in group are Administrators
        # users
        # check for user MFA device in credential report
        USER_MFA_ENABLED=$( cat $TEMP_REPORT_FILE | grep "^$auser," | cut -d',' -f8)
        if [[ "true" == $USER_MFA_ENABLED ]]; then
          textOK "$auser / MFA Enabled / admin via group $grp"
        else
          textWarn "$auser / MFA DISABLED / admin via group $grp"
        fi
      done
    else
      textNotice "$grp group provides non-administrative access"
    fi
  done
  # set +x
}

extra72(){
  # "Ensure there are no EBS Snapshots set as Public (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID72" "$TITLE72" "NOT_SCORED" "EXTRA"
  textNotice "Looking for EBS Snapshots in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_EBS_SNAPSHOTS=$($AWSCLI ec2 describe-snapshots $PROFILE_OPT --region $regx --owner-ids $ACCOUNT_NUM --output text --query 'Snapshots[*].{ID:SnapshotId}' --max-items $MAXITEMS | grep -v None 2> /dev/null)
    for snapshot in $LIST_OF_EBS_SNAPSHOTS; do
      SNAPSHOT_IS_PUBLIC=$($AWSCLI ec2 describe-snapshot-attribute $PROFILE_OPT --region $regx --output text --snapshot-id $snapshot --attribute createVolumePermission --query "CreateVolumePermissions[?Group=='all']")
      if [[ $SNAPSHOT_IS_PUBLIC ]];then
        textWarn "$regx: $snapshot is currently Public!" "$regx"
      else
        textOK "$regx: $snapshot is not Public" "$regx"
      fi
    done
  done

}

extra73(){
  # "Ensure there are no S3 buckets open to the Everyone or Any AWS user (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID73" "$TITLE73" "NOT_SCORED" "EXTRA"
  textNotice "Looking for open S3 Buckets (ACLs and Policies) in all regions...  "
  ALL_BUCKETS_LIST=$($AWSCLI s3api list-buckets --query 'Buckets[*].{Name:Name}' $PROFILE_OPT --region $REGION --output text)
  for bucket in $ALL_BUCKETS_LIST; do
    BUCKET_LOCATION=$($AWSCLI s3api get-bucket-location --bucket $bucket $PROFILE_OPT --region $REGION --output text)
    if [[ "None" == $BUCKET_LOCATION ]]; then
      BUCKET_LOCATION="us-east-1"
    fi
    if [[ "EU" == $BUCKET_LOCATION ]]; then
      BUCKET_LOCATION="eu-west-1"
    fi
    # check if AllUsers is in the ACL as Grantee
    CHECK_BUCKET_ALLUSERS_ACL=$($AWSCLI s3api get-bucket-acl $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --query "Grants[?Grantee.URI == 'http://acs.amazonaws.com/groups/global/AllUsers']" --output text |grep -v GRANTEE)
    CHECK_BUCKET_ALLUSERS_ACL_SINGLE_LINE=$(echo -ne $CHECK_BUCKET_ALLUSERS_ACL)
    # check if AuthenticatedUsers is in the ACL as Grantee, they will have access with sigened URL only
    CHECK_BUCKET_AUTHUSERS_ACL=$($AWSCLI s3api get-bucket-acl $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --query "Grants[?Grantee.URI == 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers']" --output text |grep -v GRANTEE)
    CHECK_BUCKET_AUTHUSERS_ACL_SINGLE_LINE=$(echo -ne $CHECK_BUCKET_AUTHUSERS_ACL)
    # to prevent error NoSuchBucketPolicy first clean the output controlling stderr
    TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-${bucket}.policy.XXXXXXXXXX)
    $AWSCLI s3api get-bucket-policy $PROFILE_OPT --region $BUCKET_LOCATION --bucket $bucket --output text --query Policy > $TEMP_POLICY_FILE 2> /dev/null
    # check if the S3 policy has Principal as *
    CHECK_BUCKET_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | sed -e 's/[{}]/''/g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'|awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep ^\"Principal|grep \*)
    if [[ $CHECK_BUCKET_ALLUSERS_ACL || $CHECK_BUCKET_AUTHUSERS_ACL || $CHECK_BUCKET_ALLUSERS_POLICY ]];then
      if [[ $CHECK_BUCKET_ALLUSERS_ACL ]];then
        textWarn "$BUCKET_LOCATION: $bucket bucket is open to the Internet (Everyone) with permissions: $CHECK_BUCKET_ALLUSERS_ACL_SINGLE_LINE" "$regx"
      fi
      if [[ $CHECK_BUCKET_AUTHUSERS_ACL ]];then
        textWarn "$BUCKET_LOCATION: $bucket bucket is open to Authenticated users (Any AWS user) with permissions: $CHECK_BUCKET_AUTHUSERS_ACL_SINGLE_LINE" "$regx"
      fi
      if [[ $CHECK_BUCKET_ALLUSERS_POLICY ]];then
        textWarn "$BUCKET_LOCATION: $bucket bucket policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx"
      fi
    else
      textOK "$BUCKET_LOCATION: $bucket bucket is not open" "$regx"
    fi
    rm -fr $TEMP_POLICY_FILE
  done
}

extra74(){
  # "Ensure there are no Security Groups without ingress filtering being used (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID74" "$TITLE74" "NOT_SCORED" "EXTRA"
  textNotice "Looking for Security Groups in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_SECURITYGROUPS=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --filters "Name=ip-permission.cidr,Values=0.0.0.0/0" --query "SecurityGroups[].[GroupId]" --output text --max-items $MAXITEMS)
    for SG_ID in $LIST_OF_SECURITYGROUPS; do
      SG_NO_INGRESS_FILTER=$($AWSCLI ec2 describe-network-interfaces $PROFILE_OPT --region $regx --filters "Name=group-id,Values=$SG_ID" --query "length(NetworkInterfaces)" --output text)
      if [[ $SG_NO_INGRESS_FILTER -ne 0 ]];then
        textWarn "$regx: $SG_ID has not ingress filtering and it is being used!" "$regx"
      else
        textNotice "$regx: $SG_ID has not ingress filtering but it is no being used" "$regx"
      fi
    done
  done

}

extra75(){
  # "Ensure there are no Security Groups not being used (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID75" "$TITLE75" "NOT_SCORED" "EXTRA"
  textNotice "Looking for Security Groups in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_SECURITYGROUPS=$($AWSCLI ec2 describe-security-groups $PROFILE_OPT --region $regx --query "SecurityGroups[].[GroupId]" --output text --max-items $MAXITEMS)
    for SG_ID in $LIST_OF_SECURITYGROUPS; do
      SG_NOT_USED=$($AWSCLI ec2 describe-network-interfaces $PROFILE_OPT --region $regx --filters "Name=group-id,Values=$SG_ID" --query "length(NetworkInterfaces)" --output text)
      if [[ $SG_NOT_USED -eq 0 ]];then
        textWarn "$regx: $SG_ID is not being used!" "$regx"
      else
        textOK "$regx: $SG_ID is being used" "$regx"
      fi
    done
  done
}

extra76(){
  # "Ensure there are no EC2 AMIs set as Public (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID76" "$TITLE76" "NOT_SCORED" "EXTRA"
  textNotice "Looking for AMIs in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_PUBLIC_AMIS=$($AWSCLI ec2 describe-images --owners self $PROFILE_OPT --region $regx --filters "Name=is-public,Values=true" --query 'Images[*].{ID:ImageId}' --output text)
    if [[ $LIST_OF_PUBLIC_AMIS ]];then
      for ami in $LIST_OF_PUBLIC_AMIS; do
        textWarn "$regx: $ami is currently Public!" "$regx"
      done
    else
      textOK "$regx: No Public AMIs found" "$regx"
    fi
  done
}

extra77(){
  # "Ensure there are no ECR repositories set as Public (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID77" "$TITLE77" "NOT_SCORED" "EXTRA"
  textNotice "Looking for ECR repos in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_ECR_REPOS=$($AWSCLI ecr describe-repositories $PROFILE_OPT --region $regx --query 'repositories[*].{Name:repositoryName}' --output text)
    for ecr_repo in $LIST_OF_ECR_REPOS; do
      TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-ecr-repo.policy.XXXXXXXXXX)
      $AWSCLI ecr get-repository-policy --repository-name $ecr_repo $PROFILE_OPT --region $regx --output text > $TEMP_POLICY_FILE 2> /dev/null
      # check if the policy has Principal as *
      CHECK_ECR_REPO_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep \"Principal|grep \*)
      if [[ $CHECK_ECR_REPO_ALLUSERS_POLICY ]];then
        textWarn "$regx: $ecr_repo policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx"
      else
        textOK "$regx: $ecr_repo is not open" "$regx"
      fi
    done
    rm -fr $TEMP_POLICY_FILE
  done
}

extra78(){
  # "Ensure there are no Public Accessible RDS instances (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID78" "$TITLE78" "NOT_SCORED" "EXTRA"
  textNotice "Looking for RDS instances in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_RDS_PUBLIC_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[?PubliclyAccessible==`true`].[DBInstanceIdentifier,Endpoint.Address]' --output text)
    if [[ $LIST_OF_RDS_PUBLIC_INSTANCES ]];then
      while read -r rds_instance;do
        RDS_NAME=$(echo $rds_instance | awk '{ print $1; }')
        RDS_DNSNAME=$(echo $rds_instance | awk '{ print $2; }')
        textWarn "$regx: RDS instance: $RDS_NAME at $RDS_DNSNAME is set as Publicly Accessible!" "$regx"
      done <<< "$LIST_OF_RDS_PUBLIC_INSTANCES"
      else
        textOK "$regx: no Publicly Accessible RDS instances found" "$regx"
    fi
  done
}

extra79(){
  # "Check for internet facing Elastic Load Balancers (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID79" "$TITLE79" "NOT_SCORED" "EXTRA"
  textNotice "Looking for Elastic Load Balancers in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_PUBLIC_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text)
    LIST_OF_PUBLIC_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text)
    LIST_OF_ALL_ELBS=$( echo $LIST_OF_PUBLIC_ELBS; echo $LIST_OF_PUBLIC_ELBSV2)
    LIST_OF_ALL_ELBS_PER_LINE=$( echo $LIST_OF_ALL_ELBS| xargs -n2 )
    if [[ $LIST_OF_ALL_ELBS ]];then
      while read -r elb;do
        ELB_NAME=$(echo $elb | awk '{ print $1; }')
        ELB_DNSNAME=$(echo $elb | awk '{ print $2; }')
        textWarn "$regx: ELB: $ELB_NAME at DNS: $ELB_DNSNAME is internet-facing!" "$regx"
      done <<< "$LIST_OF_ALL_ELBS_PER_LINE"
      else
        textOK "$regx: no Internet Facing ELBs found" "$regx"
    fi
  done
}

extra710(){
  # "Check for internet facing EC2 Instances (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID710" "$TITLE710" "NOT_SCORED" "EXTRA"
  textNotice "Looking for instances in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_PUBLIC_INSTANCES=$($AWSCLI ec2 describe-instances $PROFILE_OPT --region $regx --query 'Reservations[*].Instances[?PublicIpAddress].[InstanceId,PublicIpAddress]' --output text)
    if [[ $LIST_OF_PUBLIC_INSTANCES ]];then
      while read -r instance;do
        INSTANCE_ID=$(echo $instance | awk '{ print $1; }')
        PUBLIC_IP=$(echo $instance | awk '{ print $2; }')
        textWarn "$regx: Instance: $INSTANCE_ID at IP: $PUBLIC_IP is internet-facing!" "$regx"
      done <<< "$LIST_OF_PUBLIC_INSTANCES"
      else
        textOK "$regx: no Internet Facing EC2 Instances found" "$regx"
    fi
  done
}

extra711(){
  # "Check for Publicly Accessible Redshift Clusters (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID711" "$TITLE711" "NOT_SCORED" "EXTRA"
  textNotice "Looking for Reshift clusters in all regions...  "
  for regx in $REGIONS; do
    LIST_OF_PUBLIC_REDSHIFT_CLUSTERS=$($AWSCLI redshift describe-clusters $PROFILE_OPT --region $regx --query 'Clusters[?PubliclyAccessible == `true`].[ClusterIdentifier,Endpoint.Address]' --output text)
    if [[ $LIST_OF_PUBLIC_REDSHIFT_CLUSTERS ]];then
      while read -r cluster;do
        CLUSTER_ID=$(echo $cluster | awk '{ print $1; }')
        CLUSTER_ENDPOINT=$(echo $cluster | awk '{ print $2; }')
        textWarn "$regx: Cluster: $CLUSTER_ID at Endpoint: $CLUSTER_ENDPOINT is publicly accessible!" "$regx"
      done <<< "$LIST_OF_PUBLIC_REDSHIFT_CLUSTERS"
      else
        textOK "$regx: no Publicly Accessible Redshift Clusters found" "$regx"
    fi
  done
}

extra712(){
  # "Check if Amazon Macie is enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID712" "$TITLE712" "NOT_SCORED" "EXTRA"
  textNotice "No API commands available to check if Macie is enabled,"
  textNotice "just looking if IAM Macie related permissions exist.  "
  MACIE_IAM_ROLES_CREATED=$($AWSCLI iam list-roles $PROFILE_OPT --query 'Roles[*].Arn'|grep AWSMacieServiceCustomer|wc -l)
  if [[ $MACIE_IAM_ROLES_CREATED -eq 2 ]];then
    textOK "Macie related IAM roles exist, so it might be enabled. Check it out manually."
  else
    textWarn "No Macie related IAM roles found. It is most likely not to be enabled"
  fi
}

extra713(){
  # "Check if GuardDuty is enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID713" "$TITLE713" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_GUARDDUTY_DETECTORS=$($AWSCLI guardduty list-detectors $PROFILE_OPT --region $regx --output text |cut -f2)
    if [[ $LIST_OF_GUARDDUTY_DETECTORS ]];then
      while read -r detector;do
        DETECTOR_ENABLED=$($AWSCLI guardduty get-detector --detector-id $detector $PROFILE_OPT --region $regx --output text| cut -f3|grep ENABLED)
        if [[ $DETECTOR_ENABLED ]]; then
          textOK "$regx: GuardDuty detector $detector enabled" "$regx"
        else
          textWarn "$regx: GuardDuty detector $detector configured but suspended" "$regx"
        fi
      done <<< "$LIST_OF_GUARDDUTY_DETECTORS"
      else
        textWarn "$regx: GuardDuty detector not configured!" "$regx"
    fi
  done
}

extra714(){
  # "Check if CloudFront distributions have logging enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID714" "$TITLE714" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_DISTRIBUTIONS=$($AWSCLI cloudfront list-distributions $PROFILE_OPT --region $regx --query 'DistributionList.Items[].Id' --output text |grep -v "^None")
    if [[ $LIST_OF_DISTRIBUTIONS ]]; then
      for cdn in $LIST_OF_DISTRIBUTIONS;do
        CDN_LOG_ENABLED=$($AWSCLI cloudfront get-distribution $PROFILE_OPT --region $regx --id "$cdn" --query 'Distribution.DistributionConfig.Logging.Enabled' | grep true)
        if [[ $CDN_LOG_ENABLED ]];then
          textOK "$regx: CDN $cdn logging enabled" "$regx"
        else
          textWarn "$regx: CDN $cdn logging disabled!" "$regx"
        fi
      done
    else
      textNotice "$regx: No CDN configured" "$regx"
    fi
  done
}

extra715(){
  # "Check if Elasticsearch Service domains have logging enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID715" "$TITLE715" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query DomainNames --output text)
    if [[ $LIST_OF_DOMAINS ]]; then
      for domain in $LIST_OF_DOMAINS;do
        SEARCH_SLOWLOG_ENABLED=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.LogPublishingOptions.Options.SEARCH_SLOW_LOGS.Enabled --output text |grep -v ^None|grep -v ^False)
        if [[ $SEARCH_SLOWLOG_ENABLED ]];then
          textOK "$regx: ElasticSearch Service domain $domain SEARCH_SLOW_LOGS enabled" "$regx"
        else
          textWarn "$regx: ElasticSearch Service domain $domain SEARCH_SLOW_LOGS disabled!" "$regx"
        fi
        INDEX_SLOWLOG_ENABLED=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.LogPublishingOptions.Options.INDEX_SLOW_LOGS.Enabled --output text |grep -v ^None|grep -v ^False)
        if [[ $INDEX_SLOWLOG_ENABLED ]];then
          textOK "$regx: ElasticSearch Service domain $domain INDEX_SLOW_LOGS enabled" "$regx"
        else
          textWarn "$regx: ElasticSearch Service domain $domain INDEX_SLOW_LOGS disabled!" "$regx"
        fi
      done
    else
      textNotice "$regx: No Elasticsearch Service domain found" "$regx"
    fi
  done
}

extra716(){
  # "Check if Elasticsearch Service domains allow open access (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID716" "$TITLE716" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_DOMAINS=$($AWSCLI es list-domain-names $PROFILE_OPT --region $regx --query DomainNames --output text)
    if [[ $LIST_OF_DOMAINS ]]; then
      for domain in $LIST_OF_DOMAINS;do
        CHECK_IF_MEMBER_OF_VPC=$($AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.VPCOptions.Options.VPCId --output text|grep -v ^None)
        if [[ ! $CHECK_IF_MEMBER_OF_VPC ]];then
          TEMP_POLICY_FILE=$(mktemp -t prowler-${ACCOUNT_NUM}-es-domain.policy.XXXXXXXXXX)
          $AWSCLI es describe-elasticsearch-domain-config --domain-name $domain $PROFILE_OPT --region $regx --query DomainConfig.AccessPolicies.Options --output text > $TEMP_POLICY_FILE 2> /dev/null
          # check if the policy has Principal as *
          CHECK_ES_DOMAIN_ALLUSERS_POLICY=$(cat $TEMP_POLICY_FILE | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | awk '/Principal/ && !skip { print } { skip = /Deny/} '|grep \"Principal|grep \*)
          if [[ $CHECK_ES_DOMAIN_ALLUSERS_POLICY ]];then
            textWarn "$regx: $domain policy \"may\" allow Anonymous users to perform actions (Principal: \"*\")" "$regx"
          else
            textOK "$regx: $domain is not open" "$regx"
          fi
        else
          textOK "$regx: $domain is in a VPC" "$regx"
        fi
      done
    fi
    textNotice "$regx: No Elasticsearch Service domain found" "$regx"
    rm -fr $TEMP_POLICY_FILE
  done
}

extra717(){
  # "Check if Elastic Load Balancers have logging enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID717" "$TITLE717" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[*].LoadBalancerName' --output text|xargs -n1)
    LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[*].LoadBalancerArn' --output text|xargs -n1)
    if [[ $LIST_OF_ELBS || $LIST_OF_ELBSV2 ]]; then
      if [[ $LIST_OF_ELBS ]]; then
        for elb in $LIST_OF_ELBS; do
          CHECK_ELBS_LOG_ENABLED=$($AWSCLI elb describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-name $elb --query 'LoadBalancerAttributes.AccessLog.Enabled'|grep "^true")
          if [[ $CHECK_ELBS_LOG_ENABLED ]]; then
            textOK "$regx: $elb has access logs to S3 configured" "$regx"
          else
            textWarn "$regx: $elb has not access logs configured" "$regx"
          fi
        done
      fi
      if [[ $LIST_OF_ELBSV2 ]]; then
        for elbarn in $LIST_OF_ELBSV2; do
          CHECK_ELBSV2_LOG_ENABLED=$($AWSCLI elbv2 describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-arn $elbarn --query Attributes[*] --output text|grep "^access_logs.s3.enabled"|cut -f2|grep true)
          ELBV2_NAME=$(echo $elbarn|cut -d\/ -f3)
          if [[ $CHECK_ELBSV2_LOG_ENABLED ]]; then
            textOK "$regx: $ELBV2_NAME has access logs to S3 configured" "$regx"
          else
            textWarn "$regx: $ELBV2_NAME has not access logs configured" "$regx"
          fi
        done
      fi
    else
      textNotice "$regx: No ELBs found" "$regx"
    fi
  done
}

extra718(){
  # "Check if S3 buckets have server access logging enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID718" "$TITLE718" "NOT_SCORED" "EXTRA"
  LIST_OF_BUCKETS=$($AWSCLI s3api list-buckets $PROFILE_OPT --query Buckets[*].Name --output text|xargs -n1)
  if [[ $LIST_OF_BUCKETS ]]; then
    for bucket in $LIST_OF_BUCKETS;do
      BUCKET_SERVER_LOG_ENABLED=$($AWSCLI s3api get-bucket-logging --bucket $bucket $PROFILE_OPT --query [LoggingEnabled] --output text|grep -v "^None$")
      if [[ $BUCKET_SERVER_LOG_ENABLED ]];then
        textOK "Bucket $bucket has server access logging enabled"
      else
        textWarn "Bucket $bucket has server access logging disabled!"
      fi
    done
  else
    textNotice "No S3 Buckets found"
  fi
}

extra719(){
  # "Check if Route53 hosted zones are logging queries to CloudWatch Logs (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID719" "$TITLE719" "NOT_SCORED" "EXTRA"
  LIST_OF_HOSTED_ZONES=$($AWSCLI route53 list-hosted-zones $PROFILE_OPT --query HostedZones[*].Id --output text|xargs -n1)
  if [[ $LIST_OF_HOSTED_ZONES ]]; then
    for hostedzoneid in $LIST_OF_HOSTED_ZONES;do
      HOSTED_ZONE_QUERY_LOG_ENABLED=$($AWSCLI route53 list-query-logging-configs --hosted-zone-id $hostedzoneid $PROFILE_OPT --query QueryLoggingConfigs[*].CloudWatchLogsLogGroupArn --output text|cut -d: -f7)
      if [[ $HOSTED_ZONE_QUERY_LOG_ENABLED ]];then
        textOK "Route53 hosted zone Id $hostedzoneid has query logging enabled in Log Group $HOSTED_ZONE_QUERY_LOG_ENABLED"
      else
        textWarn "Route53 hosted zone Id $hostedzoneid has query logging disabled!"
      fi
    done
  else
    textNotice "No Route53 hosted zones found"
  fi
}

extra720(){
  # "Check if Lambda functions invoke API operations are being recorded by CloudTrail (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_FUNCTIONS=$($AWSCLI lambda list-functions $PROFILE_OPT --region $regx --query Functions[*].FunctionName --output text)
    if [[ $LIST_OF_FUNCTIONS ]]; then
      for lambdafunction in $LIST_OF_FUNCTIONS;do
        LIST_OF_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query trailList[?HomeRegion==\`$regx\`].Name --output text)
        if [[ $LIST_OF_TRAILS ]]; then
          for trail in $LIST_OF_TRAILS; do
            FUNCTION_ENABLED_IN_TRAIL=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $regx --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$")
            if [[ $FUNCTION_ENABLED_IN_TRAIL ]]; then
              textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx"
            else
              textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx"
            fi
          done
          # LIST_OF_MULTIREGION_TRAILS=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\`].Name" --output text)
          # if [[ $LIST_OF_MULTIREGION_TRAILS ]]; then
          #   for trail in $LIST_OF_MULTIREGION_TRAILS; do
          #     REGION_OF_TRAIL=$($AWSCLI cloudtrail describe-trails $PROFILE_OPT --region $regx --query "trailList[?IsMultiRegionTrail == \`true\` && Name == \`$trail\` ].HomeRegion" --output text)
          #     FUNCTION_ENABLED_IN_THIS_REGION=$($AWSCLI cloudtrail get-event-selectors $PROFILE_OPT --trail-name $trail --region $REGION_OF_TRAIL --query "EventSelectors[*].DataResources[?Type == \`AWS::Lambda::Function\`].Values" --output text |xargs -n1| grep -E "^arn:aws:lambda.*function:$lambdafunction$")
          #     if [[ $FUNCTION_ENABLED_IN_THIS_REGION ]]; then
          #       textOK "$regx: Lambda function $lambdafunction enabled in trail $trail" "$regx"
          #     else
          #       textWarn "$regx: Lambda function $lambdafunction NOT enabled in trail $trail" "$regx"
          #     fi
          #   done
          # else
          #   textWarn "$regx: Lambda function $lambdafunction is not being recorded!" "$regx"
          # fi
        else
          textWarn "$regx: Lambda function $lambdafunction is not being recorded no CloudTrail found!" "$regx"
        fi
      done
    else
      textNotice "$regx: No Lambda functions found" "$regx"
    fi
  done
}

extra721(){
  # "Check if Redshift cluster has audit logging enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_REDSHIFT_CLUSTERS=$($AWSCLI redshift describe-clusters $PROFILE_OPT --region $regx --query 'Clusters[*].ClusterIdentifier' --output text)
    if [[ $LIST_OF_REDSHIFT_CLUSTERS ]]; then
      for redshiftcluster in $LIST_OF_REDSHIFT_CLUSTERS;do
        REDSHIFT_LOG_ENABLED=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query LoggingEnabled --output text | grep True)
        if [[ $REDSHIFT_LOG_ENABLED ]];then
          REDSHIFT_LOG_ENABLED_BUCKET=$($AWSCLI redshift describe-logging-status $PROFILE_OPT --region $regx --cluster-identifier $redshiftcluster --query BucketName --output text)
          textOK "$regx: Redshift cluster $redshiftcluster has audit logging enabled to bucket $REDSHIFT_LOG_ENABLED_BUCKET" "$regx"
        else
          textWarn "$regx: Redshift cluster $redshiftcluster logging disabled!" "$regx"
        fi
      done
    else
      textNotice "$regx: No Redshift cluster configured" "$regx"
    fi
  done
}

extra722(){
  # "Check if API Gateway has logging enabled (Not Scored) (Not part of CIS benchmark)"
  textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA"
  for regx in $REGIONS; do
    LIST_OF_API_GW=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query items[*].id --output text)
    if [[ $LIST_OF_API_GW ]];then
      for apigwid in $LIST_OF_API_GW;do
        API_GW_NAME=$($AWSCLI apigateway get-rest-apis $PROFILE_OPT --region $regx --query "items[?id==\`$apigwid\`].name" --output text)
        CHECK_STAGES_NAME=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[*].stageName" --output text)
        if [[ $CHECK_STAGES_NAME ]];then
          for stagname in $CHECK_STAGES_NAME;do
            CHECK_STAGE_METHOD_LOGGING=$($AWSCLI apigateway get-stages $PROFILE_OPT --region $regx --rest-api-id $apigwid --query "item[?stageName == \`$stagname\` ].methodSettings" --output text|awk '{ print $1" log level "$6}')
            if [[ $CHECK_STAGE_METHOD_LOGGING ]];then
              textOK "$regx: API Gateway $API_GW_NAME has stage logging enabled for $CHECK_STAGE_METHOD_LOGGING" "$regx"
            else
              textWarn "$regx: API Gateway $API_GW_NAME logging disabled for stage $stagname!" "$regx"
            fi
          done
        else
           textWarn "$regx: No Stage name found for $API_GW_NAME" "$regx"
        fi
      done
    else
       textNotice "$regx: No API Gateway found" "$regx"
    fi
  done
}

callCheck(){
  if [[ $CHECKNUMBER ]];then
    case "$CHECKNUMBER" in
      check11|check101 ) check11;;
      check12|check102 ) check12;;
      check13|check103 ) check13;;
      check14|check104 ) check14;;
      check15|check105 ) check15;;
      check16|check106 ) check16;;
      check17|check107 ) check17;;
      check18|check108 ) check18;;
      check19|check109 ) check19;;
      check110 ) check110;;
      check111 ) check111;;
      check112 ) check112;;
      check113 ) check113;;
      check114 ) check114;;
      check115 ) check115;;
      check116 ) check116;;
      check117 ) check117;;
      check118 ) check118;;
      check119 ) check119;;
      check120 ) check120;;
      check121 ) check121;;
      check122 ) check122;;
      check123 ) check123;;
      check124 ) check124;;
      check21|check201 ) check21;;
      check22|check202 ) check22;;
      check23|check203 ) check23;;
      check24|check204 ) check24;;
      check25|check205 ) check25;;
      check26|check206 ) check26;;
      check27|check207 ) check27;;
      check28|check208 ) check28;;
      check31|check301 ) check31;;
      check32|check302 ) check32;;
      check33|check303 ) check33;;
      check34|check304 ) check34;;
      check35|check305 ) check35;;
      check36|check306 ) check36;;
      check37|check307 ) check37;;
      check38|check308 ) check38;;
      check39|check309 ) check39;;
      check310 ) check310;;
      check311 ) check311;;
      check312 ) check312;;
      check313 ) check313;;
      check314 ) check314;;
      check315 ) check315;;
      check41|check401 ) check41;;
      check42|check402 ) check42;;
      check43|check403 ) check43;;
      check44|check404 ) check44;;
      check45|check405 ) check45;;
      extra71|extra701 ) extra71;;
      extra72|extra702 ) extra72;;
      extra73|extra703 ) extra73;;
      extra74|extra704 ) extra74;;
      extra75|extra705 ) extra75;;
      extra76|extra706 ) extra76;;
      extra77|extra707 ) extra77;;
      extra78|extra708 ) extra78;;
      extra79|extra709 ) extra79;;
      extra710 ) extra710;;
      extra711 ) extra711;;
      extra712 ) extra712;;
      extra713 ) extra713;;
      extra714 ) extra714;;
      extra715 ) extra715;;
      extra716 ) extra716;;
      extra717 ) extra717;;
      extra718 ) extra718;;
      extra719 ) extra719;;
      extra720 ) extra720;;
      extra721 ) extra721;;
      extra722 ) extra722;;


      ## Groups of Checks
      check1 )
        check11;check12;check13;check14;check15;check16;check17;check18;
        check19;check110;check111;check112;check113;check114;check115;
        check116;check117;check118;check119;check120;check121;check122;
        check123;check124;
        ;;
      check2 )
        check21;check22;check23;check24;check25;check26;check27;check28
        ;;
      check3 )
        check31;check32;check33;check34;check35;check36;check37;check38;
        check39;check310;check311;check312;check313;check314;check315
        ;;
      check4 )
        check41;check42;check43;check44;check45
        ;;
      checkNotScout)
        check13;check14;check15;check16;check17;check18;check19;check114;check115;check116;check118;check122;check123;check124;check21;check23;check24;check25;check26;check27;check28;check31;check32;check33;check34;check35;check36;check37;check38;check39;check310;check311;check312;check313;check314;check315;check43;check44;check45
        ;;
      level1 )
        check11;check12;check13;check14;check15;check16;check17;check18;
        check19;check110;check111;check112;check113;check115;check116;check117;
        check118;check119;check120;check122;check123;check124;check21;check23;
        check24;check25;check26;check31;check32;check33;check34;check35;
        check38;check312;check313;check314;check315;check41;check42
        ;;
      level2 )
        check11;check12;check13;check14;check15;check16;check17;check18;
        check19;check110;check111;check112;check113;check114;check115;check116;
        check117;check118;check119;check120;check121;check122;check123;check124;
        check21;check22;check23;check24;check25;check26;check27;check28;check31;
        check32;check33;check34;check35;check36;check37;check38;check39;
        check310;check311;check312;check313;check314;check315;check41;check42;
        check43;check44;check45
        ;;
      extras )
        extra71;extra72;extra73;extra74;extra75;extra76;extra77;extra78;
        extra79;extra710;extra711;extra712;extra713;extra714;extra715;extra716;
        extra717;extra718;extra719;extra720;extra721;extra722
        ;;
      forensics-ready )
        check21;check22;check23;check24;check25;check26;check27;
        check43;
        extra712;extra713;extra714;extra715;extra717;extra718;extra719;
        extra720;extra721;extra722
        ;;
      * )
        textWarn "ERROR! Use a valid check name (i.e. check41 or extra71)\n";
    esac
    cleanTemp
    exit $EXITCODE
  fi
}

# List only check tittles

if [[ $PRINTCHECKSONLY == "1" ]]; then
  prowlerBanner
  textTitle "1" "$TITLE1" "NOT_SCORED" "SUPPORT"
  textTitle "$ID11" "$TITLE11" "SCORED" "LEVEL1"
  textTitle "$ID12" "$TITLE12" "SCORED" "LEVEL1"
  textTitle "$ID13" "$TITLE13" "SCORED" "LEVEL1"
  textTitle "$ID14" "$TITLE14" "SCORED" "LEVEL1"
  textTitle "$ID15" "$TITLE15" "SCORED" "LEVEL1"
  textTitle "$ID16" "$TITLE16" "SCORED" "LEVEL1"
  textTitle "$ID17" "$TITLE17" "SCORED" "LEVEL1"
  textTitle "$ID18" "$TITLE18" "SCORED" "LEVEL1"
  textTitle "$ID19" "$TITLE19" "SCORED" "LEVEL1"
  textTitle "$ID110" "$TITLE110" "SCORED" "LEVEL1"
  textTitle "$ID111" "$TITLE111" "SCORED" "LEVEL1"
  textTitle "$ID112" "$TITLE112" "SCORED" "LEVEL1"
  textTitle "$ID113" "$TITLE113" "SCORED" "LEVEL1"
  textTitle "$ID114" "$TITLE114" "SCORED" "LEVEL1"
  textTitle "$ID115" "$TITLE115" "NOT_SCORED" "LEVEL2"
  textTitle "$ID116" "$TITLE116" "SCORED" "LEVEL1"
  textTitle "$ID117" "$TITLE117" "SCORED" "LEVEL1"
  textTitle "$ID118" "$TITLE118" "SCORED" "LEVEL1"
  textTitle "$ID119" "$TITLE119" "SCORED" "LEVEL1"
  textTitle "$ID120" "$TITLE120" "SCORED" "LEVEL1"
  textTitle "$ID121" "$TITLE121" "NOT_SCORED" "LEVEL2"
  textTitle "$ID122" "$TITLE122" "SCORED" "LEVEL1"
  textTitle "$ID123" "$TITLE123" "NOT_SCORED" "LEVEL1"
  textTitle "$ID124" "$TITLE124" "SCORED" "LEVEL1"
  textTitle "2" "$TITLE2" "NOT_SCORED" "SUPPORT"
  textTitle "$ID21" "$TITLE21" "SCORED" "LEVEL1"
  textTitle "$ID22" "$TITLE22" "SCORED" "LEVEL2"
  textTitle "$ID23" "$TITLE23" "SCORED" "LEVEL1"
  textTitle "$ID24" "$TITLE24" "SCORED" "LEVEL1"
  textTitle "$ID25" "$TITLE25" "SCORED" "LEVEL1"
  textTitle "$ID26" "$TITLE26" "SCORED" "LEVEL1"
  textTitle "$ID27" "$TITLE27" "SCORED" "LEVEL2"
  textTitle "$ID28" "$TITLE28" "SCORED" "LEVEL2"
  textTitle "3" "$TITLE3" "NOT_SCORED" "SUPPORT"
  textTitle "$ID31" "$TITLE31" "SCORED" "LEVEL1"
  textTitle "$ID32" "$TITLE32" "SCORED" "LEVEL1"
  textTitle "$ID33" "$TITLE33" "SCORED" "LEVEL1"
  textTitle "$ID34" "$TITLE34" "SCORED" "LEVEL1"
  textTitle "$ID35" "$TITLE35" "SCORED" "LEVEL1"
  textTitle "$ID36" "$TITLE36" "SCORED" "LEVEL2"
  textTitle "$ID37" "$TITLE37" "SCORED" "LEVEL2"
  textTitle "$ID38" "$TITLE38" "SCORED" "LEVEL1"
  textTitle "$ID39" "$TITLE39" "SCORED" "LEVEL2"
  textTitle "$ID310" "$TITLE310" "SCORED" "LEVEL2"
  textTitle "$ID311" "$TITLE311" "SCORED" "LEVEL2"
  textTitle "$ID312" "$TITLE312" "SCORED" "LEVEL1"
  textTitle "$ID313" "$TITLE313" "SCORED" "LEVEL1"
  textTitle "$ID314" "$TITLE314" "SCORED" "LEVEL1"
  textTitle "$ID315" "$TITLE315" "NOT_SCORED" "LEVEL1"
  textTitle "4" "$TITLE4" "NOT_SCORED" "SUPPORT"
  textTitle "$ID41" "$TITLE41" "SCORED" "LEVEL1"
  textTitle "$ID42" "$TITLE42" "SCORED" "LEVEL1"
  textTitle "$ID43" "$TITLE43" "SCORED" "LEVEL2"
  textTitle "$ID44" "$TITLE44" "SCORED" "LEVEL2"
  textTitle "$ID45" "$TITLE45" "NOT_SCORED" "LEVEL2"
  textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT"
  textTitle "$ID71" "$TITLE71" "NOT_SCORED" "EXTRA"
  textTitle "$ID72" "$TITLE72" "NOT_SCORED" "EXTRA"
  textTitle "$ID73" "$TITLE73" "NOT_SCORED" "EXTRA"
  textTitle "$ID74" "$TITLE74" "NOT_SCORED" "EXTRA"
  textTitle "$ID75" "$TITLE75" "NOT_SCORED" "EXTRA"
  textTitle "$ID76" "$TITLE76" "NOT_SCORED" "EXTRA"
  textTitle "$ID77" "$TITLE77" "NOT_SCORED" "EXTRA"
  textTitle "$ID78" "$TITLE78" "NOT_SCORED" "EXTRA"
  textTitle "$ID79" "$TITLE79" "NOT_SCORED" "EXTRA"
  textTitle "$ID710" "$TITLE710" "NOT_SCORED" "EXTRA"
  textTitle "$ID711" "$TITLE711" "NOT_SCORED" "EXTRA"
  textTitle "$ID712" "$TITLE712" "NOT_SCORED" "EXTRA"
  textTitle "$ID713" "$TITLE713" "NOT_SCORED" "EXTRA"
  textTitle "$ID714" "$TITLE714" "NOT_SCORED" "EXTRA"
  textTitle "$ID715" "$TITLE715" "NOT_SCORED" "EXTRA"
  textTitle "$ID716" "$TITLE716" "NOT_SCORED" "EXTRA"
  textTitle "$ID717" "$TITLE717" "NOT_SCORED" "EXTRA"
  textTitle "$ID718" "$TITLE718" "NOT_SCORED" "EXTRA"
  textTitle "$ID719" "$TITLE719" "NOT_SCORED" "EXTRA"
  textTitle "$ID720" "$TITLE720" "NOT_SCORED" "EXTRA"
  textTitle "$ID721" "$TITLE721" "NOT_SCORED" "EXTRA"
  textTitle "$ID722" "$TITLE722" "NOT_SCORED" "EXTRA"
  exit $EXITCODE
fi

### All functions defined above ... run the workflow

if [[ $MODE != "csv" ]]; then
  prowlerBanner
  printColorsCode
fi
getWhoami
#genCredReport
saveReport
callCheck

textTitle "1" "$TITLE1" "NOT_SCORED" "SUPPORT"
check11
check12
check13
check14
check15
check16
check17
check18
check19
check110
check111
check112
check113
check114
check115
check116
check117
check118
check119
check120
check121
check122
check123
check124

textTitle "2" "$TITLE2" "NOT_SCORED" "SUPPORT"
check21
check22
check23
check24
check25
check26
check27
check28

textTitle "3" "$TITLE3" "NOT_SCORED" "SUPPORT"
# 3 Monitoring check commands / Mostly covered by SecurityMonkey
check31
check32
check33
check34
check35
check36
check37
check38
check39
check310
check311
check312
check313
check314
check315

textTitle "4" "$TITLE4" "NOT_SCORED" "SUPPORT"
check41
check42
check43
check44
check45

if [[ ! $EXTRAS ]]; then
  textTitle "7" "$TITLE7" "NOT_SCORED" "SUPPORT"
  extra71
  extra72
  extra73
  extra74
  extra75
  extra76
  extra77
  extra78
  extra79
  extra710
  extra711
  extra712
  extra713
  extra714
  extra715
  extra716
  extra717
  extra718
  extra719
  extra720
  extra721
  extra722
fi

cleanTemp
exit $EXITCODE
